DYSTOPIA

この記事は、 Sencha Advent Carendar 2014 の16日目の記事です。 @martini3oz がお届けします。

新しいことを覚えるというのは、大変ですが楽しいものです。

僕も先日、新しい体験をしました。THE KIDDIE というロックバンドのライブを観に行ったのです。 それはもう、これまで全然知らなかった世界でした。 とても新鮮でしたし楽しかった。KIDDIE サイコー。揺紗くんサイコー、ユウダイくんサイコー!

あ、Sencha の話でした…

Ext JS 5 から導入された ViewModel について、どのような使い方をして良いのかよくわからない、という話をよく聞きます。そこで、ViewModel について、解説したいと思います。

ViewModel は、View とデータバインディングするための仕組みです。 この DOM とデータの連携をする仕組みは、Sencha 以外のフレームワークでは大変ありがたく重要な機能のようですが、もともとコンポーネントベースで、データと画面表示の連携がすばらしい Sencha のフレームワークでは、「それって何が嬉しいの?」とか、「別にいらなくね?」という声も聞こえてきそうです。

そうはいっても、結構便利ですし、各コード量も減りますので、是非ご活用下さい。

bind

View に bind コンフィグを指定すると、そのコンフィグを、ViewModel の値とバインドできます。 バインドさせるときには、バインドさせたいコンフィグを bind で囲みます。 そして、値の名前を {} で囲んで指定します。

    bind: {
        title: '{title}'
    }

残念ながら、bind 指定できるのは、全てのコンフィグではありません。 そのコンフィグに setter が存在しているもののみになります。 そりゃそうですよね、setter が無かったら値を設定できませんものね。

また、Sencha 得意のショートハンドというかシンタックスシュガーというかも用意されています。それが、defaultBindProperty です。コンポーネントによってあらかじめ決められたコンフィグは、bind と指定するだけで済みます。

例えば、TextField では defaultbindpropertyvalue なので、

    bind: {
        value: '{firstname}'
    }

と書く代わりに

    bind: '{firstname}'

と書けます。

bind に指定できるのは、ViewModel で管理している「値」です。その値には大きく3種類あります。

data

ViewModel の中で定義した data コンフィグの内容は、bind の値となる代表格です。

ViewModel

    data: {
        title: 'Sencha Advent Carendar 2014'
    }

View

    items: [{
        xtype: 'panel',
        bind: {
            title: '{title}'
        }
    }]

ViewModel の data コンフィグの中の値を、ビューのコンフィグにバインドしています。バインドするときには、bind で囲んでやります。

formulas

data は値そのものでしたが、formulas は計算式です。 関数の検査員結果を View にバインドさせるために使います。

    formulas: {
        fullName: function(get) {
            var firstName = get('rec.firstName'),
                lastName = get('rec.lastName');
            return lastName + '  ' + firstName;
        }
    }

関数の引数として渡されるのは (上の例では get という名前になっています)、ViewModel の値を参照できる関数オブジェクトです。値の名前を渡してやると、その値を取得できます。 formulas は、他の書き方として、bind 指定で結びつける値を指定することもできます。

stores

ストアを ViewModel で管理します。stores で定義したストアを View の中にある、Grid や DataView とバインドすることができます。

Ext JS 4 までは、Application や Controller の stores コンフィグを指定すると、ストアのインスタンスをフレームワークが作成してくれましたが、それはアプリケーショングローバルでした。

ViewModel での stores 指定は、View とライフサイクルを共にする、ViewModel によって作られますので、このストアも View とライフサイクルを共にします。

    stores: {
        mystore: {
            type: 'mystore',
            autoLoad: true
        }
    }

このように指定します。 ストアの構造が結構複雑で、しかもいろんな ViewModel から使われるという場合は、app/store/ 下にストアのクラス定義を書いて、それを ViewModel の stores から利用することもできます。 その際には、ストアクラスで alias を指定して、ViewModel 側からは type 指定をすると便利です。

    Ext.define('MyApp.store.MyStore', {
        extend: 'Ext.data.Store',
        // alias 定義
        alias: 'store.mystore',
        model: 'MyApp.model.MyModel'
    });

alias のプリフィックスは store です。

    // ViewModel では
    stores: {
        mystore: {
            type: 'mystore',
            autoLoad: true
        }
    }

reference

View のコンフィグに reference を指定すると、二ついいことがあります。 一つは、ViewController 内から簡単にコンポーネントの参照を取得できることです。 これは、一度取得したコンポーネントへの参照をキャッシュするようになっているので、ComponentQuery よりも高速にオブジェクトを取得できます。

もう一つは、バインドする値として、そのコンポーネントを使うことができる、ということです。 言っている意味がわからないと思うので、コードを見て下さい。

    Ext.define('Fiddle.view.MainModel', {
        extend: 'Ext.app.ViewModel',
        alias: 'viewmodel.myview',
        data: {
            textvalue: 'Sencha'
        }
    })

    Ext.define('Fiddle.view.Main', {
        extend: 'Ext.panel.Panel',
        title: 'MVVM way',
        viewModel: 'myview',
        tbar: [{
            xtype: 'checkbox',
            reference: 'mycheck'  // reference を設定
        },{
            xtype: 'textfield',
            bind: {
                value: '{textvalue}',
                hidden: '{mycheck.checked}' // ここで使ってる!
            }
        }]
    });

この例では Checkbox に reference を設定しています。 そして、TextField の hidden にバインドしているのです。 これで、イベントハンドラーを一行も書くことなく、テキストフィールドの表示を切り替える事ができます。

どうす?便利でしょ?

バインド時のオブジェクトの扱い

ViewModel のデータとバインドすると、データが変更されたときに画面が更新されますが、データが、ネストしたオブジェクトの場合には、少し注意が必要です。 ネストしたデータをずっと追いかけているとパフォーマンスの低下を招く場合があるので、標準では、オブジェクトのプロパティの変化はモニタリングされません。

つまり、

    data: {
        leader: 'Kotsutsumi'
        worker: {
            foo: 'martini3oz',
            bar: 'tnker'
        }
    }

こんなデータの場合、leader の値が変更された場合は、変更が検知されて表示が更新されますが、worker.foo の値が変更されたときには、変更は検知されず、画面が更新されません。

このような変更を検出するためには、View で bind 指定するときに、deep コンフィグを true に設定します。

    bind: {
        data: {
            bindTo : '{worker}',
            deep   : true
        }
    }

このように指定しますと、worker オブジェクトのプロパティに変更があった場合にも、このコンポーネントは更新されるようになります。

はい、ViewModel ご理解いただけましたか? 何か質問があったらコメントよろしくお願いします。

明日は、@kotsutsumi さんです。

“DYSTOPIA” への1件の返信

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です