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みたいなものがあればいいのかなあ。