初心者のためのExtJS入門

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

(ExtJS6.5)パネル・ダイアログ[modern]

ExtJS6.5におけるパネルの変更点とダイアログ追加についてです。

パネル

collapsibleコンフィグとresizableコンフィグが追加されました。classicではどちらも実装済みの機能です。

classicの場合はboolean型の値しか指定できませんが、modernではオブジェクトリテラルで↓のように指定します。

{
    docked: 'left',
    title: 'left panel',
    width: 200,
    collapsible: {
        collapsed: true,
        direction: 'left'
    }
}

また、resizableはオブジェクトリテラルで↓のように指定します。

{
    docked: 'top',
    title: 'top panel',
    minHeight: 100,
    resizable: {
        split: true,
        edges: 'south'
    }
}

edgesでリサイズ操作でタップするエッジ部分を指定します。複数方向を指定する場合はedges: ['west', 'south']のように配列指定するようです。

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

ダイアログ

Ext.Dialogというダイアログ用のクラスが追加されました。ウィンドウ

var dialog = Ext.create({
    xtype: 'dialog',
    title: 'Dialog',

    maximizable: true,
    html: 'Content<br>goes<br>here',

    buttons: {
        ok: function () {  // standard button (see below)
            dialog.destroy();
        }
    }
});

dialog.show();

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

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

Ext.Panelのサブクラスなのでアイテムコンポーネントも指定できます。Ext.MessageBoxよりも柔軟なことができそうですね。

ボタンはbuttonsコンフィグで↓のようにokやcancelをキーとしたオブジェクトリテラルで指定します。

buttons: {
    ok: 'onOK',
    cancel: 'onCancel'
}

buttonsコンフィグの値はstandardButtonsコンフィグの値とマージされて、↓がデフォルト値となっているbuttonToolbarコンフィグに渡されます。

buttonToolbar: {
    xtype: 'toolbar',
    itemId: 'buttonToolbar',
    docked: 'bottom',
    defaultType: 'button',
    weighted: true,
    ui: 'footer',
    defaultButtonUI: 'action',

    layout: {
        type: 'box',
        vertical: false,
        pack: 'center'
    }
}

weighted: trueとなっているので、ボタンの並び順はweightで変更できますね。

(ExtJS6.5)フローティングメニューの追加・weightedコンフィグ[modern]

今回は小ネタ2つです。

メニュー

これまでmodernのメニューといえば、ActionSheetを画面外からスライドイン(http://examples.sencha.com/extjs/6.5.0/examples/kitchensink/?modern#actionsheets)する形式がほとんどでしたが、今回classicのようなフローティング形式のメニューが使えるようになりました。これもmodernをPCブラウザでも使えるようにという点から追加されたものでしょう。

classicにとってはあまり目新しい機能があるわけではありませんが、↓のような感じで使います。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    items: [
        {
            xtype: 'button',
            text: 'menu',
            menu: {
                floated: false,
                items: [
                    {
                        text: 'item1'
                    },
                    {
                        text: 'item2(disabled)',
                        disabled: true
                    },
                    {
                        text: 'item3(uncheck)',
                        checked: false,
                        separator: true
                    },
                    {
                        text: 'item4(checked)',
                        checked: true
                    },
                    {
                        text: 'item5',
                        menu: {
                            items: [
                                {
                                    text: 'subitem1'
                                },
                                {
                                    text: 'subitem2'
                                }
                            ]
                        }
                    },
                    {
                        group: 'mygroup',
                        text: 'item6(group)',
                        value: 'item6',
                        checked: true,
                        separator: true
                    },
                    {
                        group: 'mygroup',
                        text: 'item7(group)',
                        value: 'item7'
                    },
                    {
                        group: 'mygroup',
                        text: 'item8(group)',
                        value: 'item8'
                    },
                    {
                        text: 'Open Google',
                        href: 'http://www.google.com',
                        separator: true
                    }
                ]
            }
        }
    ]
});

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

Exampleでは、Ext.menu.Itemのtargetコンフィグを使うようなコードが紹介されていてtarget: '_blank'としてありましたが、効きませんでした。そりゃまあバグは残ってますよね。

Ext.Containerのweightedコンフィグ

Ext.Containerのweightedコンフィグをtrueにすると、アイテムコンポーネントに指定されたweightの値で、順番を制御できるようになります。これもclassicでは既に存在する設定で、modernでも使えるようにしたようですね。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    layout: 'fit',

    items: [
        {
            xtype: 'container',
            layout: 'vbox',
            weighted: true,
            items: [
                {
                    xtype: 'button',
                    text: 'ボタン1',
                    weight: 20
                },
                {
                    xtype: 'button',
                    text: 'ボタン2',
                    weight: 10
                }
            ]
        }
    ]
});

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

weightが小さいほうがより先頭位置になります。

(ExtJS6.5)フォームの新機能[modern] (2)

今回までフォーム関連の新機能についてです。

バイスに応じて日付フィールドの入力方法が切り替わる

これまでmodernでの日付入力といえば、画面下部からスライドインするパネルでドラムをクルクル回して日付を選択するというものでした。

ExtJS6.5になっても、モバイルのブラウザで操作する場合は基本的には変わらないのですが、PCブラウザで操作する場合には、classicと同じ入力形式に切り替わるようになりました。

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

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

ドラムでの入力ってもう古いと思うんですが、どうなんでしょうね?個人的には、画面中央に日付選択用のダイアログが表示されたほうが入力しやすいんですが・・。

あと、時間入力用のフィールドを作ってほしいな・・。あのアナログ時計形式のやつ。

ExtJS7に期待します。

あと、コンボボックスも同じようにデバイスに応じて切り替わるみたいです。

classic形式の日付入力では、Ext.panel.Dateという新しいクラスが使用されています。

datefieldのfloatedPickerコンフィグで指定されており、設定を変えたい場合はここに直接指定すれば良さそうです。

floatedPicker: {
    xtype: 'datepanel',
    autoConfirm: true,
    floated: true,
    panes: 3,
    listeners: {
        tabout: 'onTabOut',
        scope: 'owner'
    },
    keyMap: {
        ESC: 'onTabOut',
        scope: 'owner'
    }
}

↑のようにpanes: 3を指定すると、範囲が3か月分に広がりました。

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

lookupNameメソッド

入力フィールドにはほぼnameコンフィグを指定しますが、nameコンフィグの値でフィールドを参照できるlookupNameメソッドが追加されました。

中身を見た感じ、一度参照を取得したらnameRefsプロパティにキャッシュしているようです。

たしかにあると便利かもしれないですね。

ちなみに、ラジオボタンのように同じnameの値が指定される場合は、配列で戻ってくるみたいですよ。

フォームレイアウト

この変更については、いまいちピンとこなかったんですが、フィールドのラベルの幅が自動的にいい感じのサイズに調整されますよってことだと思ってます。

該当箇所: http://docs.sencha.com/extjs/6.5.0/guides/whats_new/whats_new.html#whats_new--whats_new-_form_layout

(ExtJS6.5)フォームの新機能[modern] (1)

今回からExtJS6.5のフォームの変更点を挙げていきます。

バリデーション

バリデーションは大きく変わり、classicに寄せられました。

以前のバリデーション機能はモデル側で実装されており、フォームの各フィールドで設定できるのはごくわずかでした。

しかし、今回の変更でフィールドに直接バリデーションの内容を指定できるようになり、また、エラーメッセージも表示されるようになっています(ようやく)。

/**
 * ユーザ登録フォームクラス。
 *
 * @class Sample.view.user.regist.Form
 * @extend Ext.form.Panel
 */
Ext.define('Sample.view.user.regist.Form', {
    extend: 'Ext.form.Panel',
    xtype: 'user_regist_form',

    requires: [
        'Ext.field.Text',
        'Ext.field.Date',
        'Ext.field.Radio',
        'Ext.field.Container',
        'Ext.data.validator.Email',
        'Sample.view.user.regist.FormViewController'
    ],

    controller: 'user_regist_form',

    cls: 'user-regist-form',

    title: 'ユーザ登録',

    items: [
        {
            xtype: 'emailfield',
            label: 'メールアドレス',
            name: 'mailAddress',
            required: true,
            maxLength: 255,
            validators: 'email'
        },
        {
            xtype: 'textfield',
            label: '氏名',
            name: 'name',
            required: true
        },
        {
            xtype: 'textfield',
            label: 'フリガナ',
            name: 'kana',
            required: true
        },
        {
            xtype: 'datefield',
            label: '生年月日',
            name: 'birthday'
        },
        {
            xtype: 'containerfield',
            layout: 'vbox',
            label: '性別',
            margin: '20 0 0',
            items: [
                {
                    xtype: 'radiofield',
                    name: 'gender',
                    label: '男性',
                    value: 1
                },
                {
                    xtype: 'radiofield',
                    name: 'gender',
                    label: '女性',
                    value: 2
                }
            ]
        }
    ],

    buttons: {
        save: {
            text: '登録',
            ui: 'action',
            handler: 'onClickSaveButton'
        }
    }
});

/**
 * ユーザ登録フォームのビューコントローラクラス。
 *
 * @class Sample.view.user.regist.FormViewController
 * @extend Ext.app.ViewController
 */
Ext.define('Sample.view.user.regist.FormViewController', {
    extend: 'Ext.app.ViewController',
    alias: 'controller.user_regist_form',

    /**
     * 保存ボタンクリック時の処理。
     */
    onClickSaveButton: function () {
        var me = this,
            view = me.getView();

        if (view.validate()) {
            // TODO: エラーが無い場合の処理をここに記述
        }
    }
});

f:id:sham-memo:20170610153813p:plain f:id:sham-memo:20170610153827p:plain

複数のチェックがある場合はvalidatorsに配列を指定できます。

validators: [
    'url',
    { type: 'length', max: 140 }
]

モデルで定義していたバリデーション定義を、そのままフィールドに移した感じです。

バリデーション用のクラスは、Ext.data.validatorパッケージに存在します。

また、エラーメッセージの表示はフィールドのerrorTargetコンフィグで変更できます。

たとえば、errorTarget: 'under'とすれば↓のように表示されます。

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

いやー、ようやくclassicに追いつきましたね!

Ext.field.InputMask

入力形式のアシスト機能として、Ext.field.InputMaskが実装されました。

どういうことができるかというと、↓のカード番号入力のようなことです。(こういう入力って一般的な名称があるんでしょうか・・)

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

時々、サポートしているサイトがありますが、入力しやすくて便利ですよね。これがサポートされたのはうれしい限りです。

もう少しUIとかスタイルをキレイにできるのかな?

(ExtJS6.5)associatedDataコンフィグ[modern]

ExtJS6.5で、Ext.dataview.AbstractクラスにassociatedDataコンフィグが追加されました。

Ext.dataview.Abstractは、リストやグリッドの基底クラスです。

リストやグリッドのようなデータビューのレンダリング処理では、ストアのモデルのデータをitemTplコンフィグで指定されたテンプレート内の変数に埋め込みます。

その際、アソシエーション付けしたフィールドがあると、レンダリング処理の中でそのデータも全て取得しようとします。

はじめから表示する項目にアソシエーションのデータが含まれないのであれば、それを除外できたほうがパフォーマンス上有利です。

associatedDataコンフィグを使うとその設定を実現できます。

モデルとストア

Ext.define('Sample.model.Address', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'state',
            type: 'string'
        },
        {
            name: 'cities',
            type: 'string'
        }
    ]
});

Ext.define('Sample.model.User', {
    extend: 'Ext.data.Model',

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

    fields: [
        {
            name: 'id',
            type: 'int'
        },
        {
            name: 'name',
            type: 'string'
        },
        {
            name: 'tel',
            type: 'string'
        },
        {
            name: 'age',
            type: 'int'
        },
        {
            name: 'address',
            reference: 'Sample.model.Address'
        }
    ]
});

Ext.define('Sample.store.User', {
    extend: 'Ext.data.Store',

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

    model: 'Sample.model.User',

    proxy: 'memory',

    data: [
        {id: 1, name: 'yamada1', tel: '09011111111', age: 35, address: {state: '鹿児島県', cities: '鹿児島市1'}},
        {id: 2, name: 'yamada2', tel: '09022222222', age: 28, address: {state: '鹿児島県', cities: '鹿児島市2'}},
        {id: 3, name: 'yamada3', tel: '09033333333', age: 24, address: {state: '鹿児島県', cities: '鹿児島市3'}},
        {id: 4, name: 'yamada4', tel: '09044444444', age: 20, address: {state: '鹿児島県', cities: '鹿児島市4'}},
        {id: 5, name: 'yamada5', tel: '09055555555', age: 62, address: {state: '熊本県', cities: '熊本市1'}},
        {id: 6, name: 'yamada6', tel: '09066666666', age: 61, address: {state: '熊本県', cities: '熊本市2'}},
        {id: 7, name: 'yamada7', tel: '09077777777', age: 40, address: {state: '熊本県', cities: '熊本市3'}},
        {id: 8, name: 'yamada8', tel: '09088888888', age: 38, address: {state: '熊本県', cities: '熊本市4'}},
        {id: 9, name: 'yamada9', tel: '09099999999', age: 32, address: {state: '熊本県', cities: '熊本市5'}}
    ]
});

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit',
        'Ext.dataview.List',
        'Sample.store.User'
    ],

    controller: 'main',

    layout: 'fit',

    title: 'ユーザ一覧',

    items: {
        xtype: 'list',

        store: 'User',

        itemTpl: [
            '{name}<br>{address.state}{address.cities}'
        ]
    }
});

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

特に何も設定していない場合は、↑となります。addressがアソシエーションの項目です。

associatedData: falseを指定すると、全てのアソシエーションを除外します。

items: {
    xtype: 'list',

    store: 'User',

    itemTpl: [
        '{name}<br>{address.state}{address.cities}'
    ],

    associatedData: false
}

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

XTemplate evaluation exception: Cannot read property 'state' of undefined となり、addressがundefinedになっていることがわかります。

特定の項目だけ取得したいときは↓のようにするようです。

associatedData: {
  address: true
}

ちなみに、address: falseでも同じ結果になりました。バグっぽいね。

(ExtJS6.5)リストの新機能[modern]

今回はリストの追加機能を取り上げます。

行のスワイプ操作

Ext.dataview.listswiper.ListSwiperというプラグインクラスを使って、リストの行をスワイプするアクションが簡単に実装できるようになりました。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit',
        'Ext.dataview.List',
        'Ext.dataview.listswiper.ListSwiper'
    ],

    controller: 'main',

    layout: 'fit',

    title: 'ユーザ一覧',

    items: {
        xtype: 'list',

        store: {
            proxy: 'memory',

            fields: [
                'id',
                'state',
                'name',
                'tel',
                'age'
            ],

            data: [
                { id: 1, state: '鹿児島県', name: 'yamada1', tel: '09011111111', age: 35 },
                { id: 2, state: '鹿児島県', name: 'yamada2', tel: '09022222222', age: 28 },
                { id: 3, state: '鹿児島県', name: 'yamada3', tel: '09033333333', age: 24 },
                { id: 4, state: '鹿児島県', name: 'yamada4', tel: '09044444444', age: 20 },
                { id: 5, state: '熊本県', name: 'yamada5', tel: '09055555555', age: 62 },
                { id: 6, state: '熊本県', name: 'yamada6', tel: '09066666666', age: 61 },
                { id: 7, state: '熊本県', name: 'yamada7', tel: '09077777777', age: 40 },
                { id: 8, state: '熊本県', name: 'yamada8', tel: '09088888888', age: 38 },
                { id: 9, state: '熊本県', name: 'yamada9', tel: '09099999999', age: 32 }
            ]
        },

        itemTpl: [
            '{name}'
        ],

        plugins: {
            listswiper: {
                defaults: {
                    width: 48
                },

                right: [{
                    iconCls: 'x-fa fa-envelope',
                    ui: 'alt confirm',
                    commit: 'onMessage'
                }, {
                    iconCls: 'x-fa fa-phone',
                    ui: 'alt action',
                    commit: 'onCall'
                }, {
                    iconCls: 'x-fa fa-trash',
                    ui: 'alt decline',
                    commit: 'onDeleteItem',
                    undoable: true
                }]
            }
        }
    }
});

f:id:sham-memo:20170605231956p:plain f:id:sham-memo:20170605232009p:plain

行をスワイプすると、右側からボタンが現れます。いいですね。こういうのを見ると、すぐ導入したくなっちゃいます。こういう機能追加は大歓迎です。

さらにExt.dataview.listswiper.Stepperを使って、もう少し違う形式にもなります。

plugins: {
    listswiper: {
        widget: {
            xtype: 'listswiperstepper'
        },

        defaults: {
            width: 48
        },

        right: [{
            iconCls: 'x-fa fa-envelope',
            ui: 'alt confirm',
            commit: 'onMessage'
        }, {
            iconCls: 'x-fa fa-phone',
            ui: 'alt action',
            commit: 'onCall'
        }, {
            iconCls: 'x-fa fa-trash',
            ui: 'alt decline',
            commit: 'onDeleteItem',
            undoable: true
        }]
    }
}

重複するのでpluginsコンフィグだけ抜粋です。

f:id:sham-memo:20170605231956p:plain f:id:sham-memo:20170605234107p:plain f:id:sham-memo:20170605234051p:plain f:id:sham-memo:20170605234115p:plain

これは、スワイプ途中で離すとアクションが発生するようです。画像2枚目の状態で指を離すと、onMessageメソッドが実行されるようになっています。

なんかすごいですね。AndroidiOSAPIにはあまり詳しくないですが、こういう機能が標準搭載されているのでしょうか?

一応グリッドでも使えるみたいですね。横スクロールがある場合、あんまりよろしくないようですが。

Pull Refresh

リストが一番上にある状態で、さらにスクロールさせることで最新データをリロードさせる操作ですね。

Ext.dataview.pullrefresh.PullRefreshプラグインクラスを使います。

ExtJS6.2だと、ブラウザの標準機能に影響を受けて正しく機能しなかったため全然使ってませんでしたが、今回は大丈夫そうです。スマフォで試してみても動作しているようでした。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit',
        'Ext.dataview.List',
        'Ext.dataview.pullrefresh.PullRefresh'
    ],

    controller: 'main',

    layout: 'fit',

    title: 'ユーザ一覧',

    items: {
        xtype: 'list',

        store: {
            proxy: 'memory',

            fields: [
                'id',
                'state',
                'name',
                'tel',
                'age'
            ],

            data: [
                {id: 1, state: '鹿児島県', name: 'yamada1', tel: '09011111111', age: 35},
                {id: 2, state: '鹿児島県', name: 'yamada2', tel: '09022222222', age: 28},
                {id: 3, state: '鹿児島県', name: 'yamada3', tel: '09033333333', age: 24},
                {id: 4, state: '鹿児島県', name: 'yamada4', tel: '09044444444', age: 20},
                {id: 5, state: '熊本県', name: 'yamada5', tel: '09055555555', age: 62},
                {id: 6, state: '熊本県', name: 'yamada6', tel: '09066666666', age: 61},
                {id: 7, state: '熊本県', name: 'yamada7', tel: '09077777777', age: 40},
                {id: 8, state: '熊本県', name: 'yamada8', tel: '09088888888', age: 38},
                {id: 9, state: '熊本県', name: 'yamada9', tel: '09099999999', age: 32}
            ]
        },

        itemTpl: [
            '{name}'
        ],

        plugins: {
            pullrefresh: true
        }
    }
});

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

pullrefresh: trueを指定すると、↓のようにpullrefreshspinnerを使うようです。

plugins: {
    pullrefresh: {
        widget: 'pullrefreshspinner',
        overlay: true
    }
}

ほかにもpullrefreshbarを使う方法もあるようです。

plugins: {
    pullrefresh: {
        widget: 'pullrefreshbar',
        overlay: false
    }
}

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

たしか元々のUIはこれでしたね。スピナーのほうは無かったと思います。

(ExtJS6.5)グリッドの新機能[modern] (3)

今回もグリッドについてです。細かいところばかりなので、前回書いとけば良かったです。

Virtual Stores

Ext.data.virtual.Storeというクラスが追加されたようです。

BufferedStoreと同じようなクラスであるとの記述があります。

管理するデータセットのうち必要なレコードおよびその前後のレコードをロード・保持できるようです。

たしかにBufferedStoreと同じような気がしますが、残念ながら私自身は具体的な利用箇所をイメージできていません。

いくつか違いがあるようですが、そのうち「複数のビューで共有可能」という点があるので、VirtualStoreのほうが応用効くのかな。(そもそもBufferedStoreが共有できないことを知らなかったですが)

もう少し情報を仕入れてから判断したいところです。

選択用クラス名が変わってる

名前変わっただけじゃなくて、いくつか機能も追加されているようです。

Ext.grid.plugin.MultiSelection -> Ext.grid.plugin.RowOperations

グルーピング

グリッドのgroupHeaderコンフィグでテンプレートなどを指定するようになりました。

前回しれっとグルーピングしているので、具体的なコードはそちらで確認できます。

http://sham-memo.hatenablog.com/entry/2017/06/02/200247

グリッドのサマリー

Ext.grid.SummaryRowを使いますが、groupFooterかpinnedFooterコンフィグに指定するようになりました。

年齢を追加して、平均年齢を表示させてみました。

Ext.define('Sample.view.main.Main', {
    extend: 'Ext.Panel',
    xtype: 'app-main',

    requires: [
        'Ext.layout.Fit'
    ],

    layout: 'fit',

    title: 'ユーザ一覧',

    items: {
        xtype: 'grid',

        store: {
            proxy: 'memory',

            fields: [
                'id',
                'state',
                'name',
                'tel',
                {
                    name: 'age',
                    summary: 'average'
                }
            ],

            data: [
                { id: 1, state: '鹿児島県', name: 'yamada1', tel: '09011111111', age: 35 },
                { id: 2, state: '鹿児島県', name: 'yamada2', tel: '09022222222', age: 28 },
                { id: 3, state: '鹿児島県', name: 'yamada3', tel: '09033333333', age: 24 },
                { id: 4, state: '鹿児島県', name: 'yamada4', tel: '09044444444', age: 20 },
                { id: 5, state: '熊本県', name: 'yamada5', tel: '09055555555', age: 62 },
                { id: 6, state: '熊本県', name: 'yamada6', tel: '09066666666', age: 61 },
                { id: 7, state: '熊本県', name: 'yamada7', tel: '09077777777', age: 40 },
                { id: 8, state: '熊本県', name: 'yamada8', tel: '09088888888', age: 38 },
                { id: 9, state: '熊本県', name: 'yamada9', tel: '09099999999', age: 32 }
            ],

            grouper: {
                property: 'state'
            }
        },

        grouped: true,

        groupHeader: {
            tpl: 'Group: {name} ({count})'
        },

        groupFooter: {
            xtype: 'gridsummaryrow',
            cls: 'user-summaryrow'
        },

        columns: [
            {
                dataIndex: 'id',
                text: 'ID',
                align: 'right'
            },
            {
                dataIndex: 'name',
                text: '氏名',
                minWidth: 150,
                flex: 1
            },
            {
                dataIndex: 'tel',
                text: '電話番号',
                width: 200
            },
            {
                dataIndex: 'age',
                text: '年齢',
                width: 150,
                align: 'right',
                summaryCell: 'numbercell'
            }
        ]
    }
});

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

pinnedFooterコンフィグを使う場合は、pinFooters: trueも併せて指定します。pinnedFooterを使うと、フッター部分をピン留めするようにグリッドのスクロール時にサマリー行が下部に留まってくれるようです。

あと、試しにサマリー行の背景色を変えようとして気づいたことがあります。

scssファイルの配置場所がjsファイルと同じところになってるようです。Main.jsと同じディレクトリにMain.scssを作成したら確かに反映されました。ExtJS7でもこれでいくのかな。

レーティング

Ext.ux.rating.Pickerで5段階のレーティングを表現できます。標準装備に加わりました。

 

 

他にもあるようですが、グリッドは以上で。次はリストを見てみるつもりです。