Ext JS 4のMVC Application Architectureを試してみた

先日ついにリリースされたExt JS 4に新たに追加されたMVCアーキテクチャを試してみました。
MVC Application Architecture

MVCアーキテクチャで自作したサンプル

見よう見まねなのでいろいろとアレなところがあります。

調べたことの簡単なまとめ

Ext JS 4のMVCアーキテクチャ
  • モデル → Ext.data.Modelクラスを継承したクラス
  • ビュー → パネル、グリッド、ツリーなどのコンポーネント
  • コントローラー → Ext.app.Controllerクラスを継承したクラス
アプリケーションのディレクトリ構造
- appname
    - app
        - controller
        - model
        - store
        - view
    - resources
        - css
        - images
        - ...
    - app.js
    - index.html

appnameはアプリケーションの名前のディレクトリです。
アプリケーションはindex.htmlからロードするapp.jsで起動します。
このディレクトリ構造に沿うことで動的なJSファイルのロードや記述の簡略化などのフレームワークの恩恵を受けることができます。

index.html
<html>
  <head>
    <title>Contacts</title>

    <link rel="stylesheet" type="text/css" href="../ext-4.0.0/resources/css/ext-all.css" />

    <link rel="stylesheet" type="text/css" href="resources/css/app.css" />

    <script type="text/javascript" src="../ext-4.0.0/ext-debug.js"></script>

    <script type="text/javascript" src="app.js"></script>

  </head>
  <body></body>
</html>

index.htmlからロードするのは

だけです。
その他のファイルはフレームワークがロードしてくれます。

app.js
Ext.application({
    name: 'Contacts',

    appFolder: 'app',

    controllers: [
        'Contacts'
    ],

    launch: function() {
        Ext.create('Ext.container.Viewport', {

            layout: 'card',

            items: [
                { xtype: 'contactlist' },
                { xtype: 'contactnew' },
                { xtype: 'contactedit' }
            ]
        });
    }
});

app.jsには

  • nameにアプリケーションの名前を定義します。
  • appFolderにアプリケーションの格納フォルダ(デフォルト:app)を指定します。
  • controllerにアプリケーションのコントローラー(複数可)を指定します。
  • launchに起動ファンクションを定義します。
Controller
Ext.define('Contacts.controller.Contacts', {
    extend: 'Ext.app.Controller',

    requires: [
        'Ext.layout.container.Card',
        'Ext.layout.container.Border',
        'Ext.data.proxy.Rest',
        'Ext.form.field.Hidden'
    ],

    views: [
        'contact.List',
        'contact.Grid',
        'contact.New',
        'contact.Edit',
        'contact.Form'
    ],

    stores: [
        'Contacts'
    ],

    models: [
        'Contact'
    ],

    init: function() {
        console.log('controller init');

        this.control({
            'contactgrid > toolbar > button[action=new]': {
                click: function(button) {
                    button.up('viewport').getLayout().setActiveItem(1); 
                }
            },
        //snip
      })
   }
})

Controllerに定義することはモリモリあります。

  • requiresで必要なレイアウトなどを指定します(これはいいのか不明)。
  • viewsでアプリのビューを指定します。
  • storesでアプリで使うStoreを指定します。
  • modelsでアプリのモデルを指定します。
  • initファンクションはアプリの起動前に呼び出されます。
  • controlファンクションにはビューで発生するイベントのリスナーを定義できます。

イベントが発生するコンポーネント
'contactgrid > toolbar > button[action=new]'
です。
CSSセレクタ風にExt JSコンポーネントを指定できます。
このコンポーネントのclickイベントをリスンしています。

View
Ext.define('Contacts.view.contact.List', {
    extend: 'Ext.Panel',

    alias: 'widget.contactlist',

    layout: 'border',

    defaults: {
        border: false
    },

    initComponent: function() {
        var me = this;

        Ext.apply(me, {
            items: [{
                region: 'north',
                height: 50
            },
            {
                region: 'west',
                width: 200
            },
            {
                region: 'south',
                height: 200
            },
            {
                region: 'center',

                items: [
                    {xtype: 'contactgrid'}
                ]
            }]
            
        });

        this.callParent(arguments);
    }
});

ViewはExt JSコンポーネントです。
基本的にExt JS3のときと同じように書けます。
明らかに変わったのは、xtypeの定義とinitComponentファンクションからのスーパークラスをコールするファンクションでしょうか。
xtypeは下記のように定義します。

alias: 'widget.contactlist',

widgetは必須のprefixです。他からこのコンポーネントをxtypeで指定するときは

xtype: 'contactlist'

になります。

スーパークラスをコールするファンクションはcallParentファンクションに変わっています。

ModelとStore
Ext.define('Contacts.model.Contact', {
    extend: 'Ext.data.Model',

    fields: [
        "_docId",
        "givenName",
        "familyName",
        "emails",
        "phoneNumbers",
    ],

    proxy: {
        type: 'rest',

        url: '/_je/myDoc'
    }
});

Modelはproxyを持ってサーバー通信したり、他のモデルとhas_manyなどの関連をもったり、バリデーションの機能があったりするようですが未検証です。

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

    model: 'Contact',

    fields: [
        "_docId",
        "givenName",
        "familyName",
        "emails",
        "phoneNumbers",
    ],

    proxy: {
        type: 'rest',

        url: '/_je/myDoc'
    },

    autoLoad: true
});

StoreはModelの集約として使えます。Ext JS3からかなり変わったみたいですが未検証。

まとめ
  • Ext JS 4のMVCアーキテクチャは規約に沿うことにより開発者が記述するコード量を減らそうとしている。モダンな構成と思う。メタプログラミングが多用されている。
  • MVCアーキテクチャに限らず)「なにかをやる」ための手段が多様化した。
  • Railsのscaffoldみたいなものがあればいいのかなあ。

Ext JS4のExt.define

クラス設定値のオブジェクトには下記の設定を指定できます。(デフォルト)

ExtJS4のクラス定義まわり - S5

さっそく間違えてました。
requiresとusesが漏れてました。
(Beta2で確認しました)

Ext.define('My.awesome.Hoge',{
       requires: [
           'Ext.panel.Panel',
           'Ext.button.Button'
       ],

       uses: [
           'Ext.grid.GridPanel'
       ]
);

requiresにはロードするクラスを配列で定義します。
クラス名からロードするJSファイルを探して、非同期でロードする...ようです。

usesも同じようにクラスのロードのはずですが、ざっとみたところでは整理できてないです。

ExtJS4のクラス定義まわり

※ここの内容はExtJS4 beta1で確認しました。正式リリース版では変わるかもしれません

Ext.define

Ext JS4でクラスを定義するにはExt.defineをコールします。
Ext.defineは3つの引数を取ります。

  1. (パッケージ名含む)クラス名の文字列
  2. クラス設定値のオブジェクト
  3. クラスを定義したときのコールバック関数

クラス設定値のオブジェクトには下記の設定を指定できます。(デフォルト)

  • alias
    • クラスのxtypeを定義します
  • singleton
    • クラスをシングルトンにします
  • alternateClassName
    • クラス名の別名を定義します
Ext.define('My.awesome.Foo1',{ 
        foo1: function(){
            console.log("this is foo1");
        }
    });

Ext.define('My.awesome.Foo2',{ 
        foo2: function(){
            console.log("this is foo2");
        }
    });

Ext.define('My.awesome.Bar',{ 
});


Ext.define('My.awesome.Hoge',{

        //My.awesome.Barクラスを継承します
        extend: 'My.awesome.Bar',

    //My.awesome.Foo1クラスのfoo1メソッドとMy.awesome.Foo2のfoo2メソッドをこのクラスのインスタンスメソッドにします
        mixins: {
            foo1: My.awesome.Foo1,
            foo2: My.awesome.Foo2
        },

    //属性「fuga」と「piyo」を定義します
        config: {
            fuga: 100,
            piyo: "hello"
        },

    //クラスのスタティック属性を定義します
        statics: {
            say: function(){
                console.log("this is static");
            }
        },

    //コンストラクタ
        constructor: function(config) {
            this.initConfig(config);
            return this;
        }
    },
    function(cls){
        console.log('クラスを定義したときのコールバックです');
    });

Ext.create

クラスをインスタンス化するにはExt.createをコールします。
Ext.createは2つの引数を取ります。

  1. (パッケージ名含む)クラス名の文字列
  2. 引数のオブジェクト
var hoge = Ext.create('My.awesome.Hoge',{
        fuga: 200
    });

hoge.foo1();
hoge.foo2();
console.log(hoge.getFuga());
console.log(hoge.getPiyo());
My.awesome.Hoge.say();

実行結果

//クラスを定義したときのコールバック関数
クラスを定義したときのコールバックです

//mixinsで取り込んだメソッド
this is foo1
this is foo2

//インスタンス属性
200
hello

//スタティック属性
this is static

Ghost Trick

逆転裁判のプロデューサー(?)の方が手がけたゲームです。
登場人物の会話の内容やテンポは完全に逆転裁判カラーが出てます。
逆転裁判好きなら間違いなく楽しめると思います。(ちょっと濃さが足りないと感じるかも)
iPhone版も評判いいらしい。

ゴースト トリック

ゴースト トリック

進撃の巨人を読みました

[book]「このマンガがすごい2010」で1位をとった進撃の巨人を読みました。
Amazonのレビューで書き尽くされている感じがありますが、面白いです。読み始めると入り込みます。作者の書きたくてしょうがない感が伝わってくるようなアツい作品です。

進撃の巨人(1) (少年マガジンKC)

進撃の巨人(1) (少年マガジンKC)


進撃の巨人(2) (講談社コミックス)

進撃の巨人(2) (講談社コミックス)


進撃の巨人(3) (講談社コミックス)

進撃の巨人(3) (講談社コミックス)