2012年8月19日日曜日

banchaを触ってみた4

疎通させてみる

Bancha使ってみたくて
cakeをざっと流し読みしたけど、
まだ疎通できてないので疎通させてみようと思います。
お題はBanchaのサンプルをMVCに再配置、と、
テスト的にコンポーネントからイベント発火させて
Mainコントローラでディスパッチするようにしてみました。
A.View(何らかの操作)

Aコントローラ(ビューをコンポーネントクエリで引っ掛けて)
Ext既存イベントの処理→A.fireEventで独自イベント発行

Mainコントローラ(ビューをコンポーネントクエリで引っ掛けて)
独自インベントのディスパッチ
AコントローラをgetControllerで取得してAコントローラ.実装処理を呼び出し
なかんじで複数コントローラ時な感じを想定して見ました。

アプリの作成

ここからはExtJS4の話になります。
xframeworkであればwebapp下にsrcとか掘ってアプリを設置するんですが、
Cakeで言うところだと、、、
webroot/js/srcとかになるのでしょうかね。
あとは株式会社ゼノフィさんの技術ページ(code:x)か@martini3ozさんの
ページを見ながら実践開発ガイドのクラスローダ部分を読み返せばスタートアップ設置はOKです。
大掛かりな設置をすると大変なので
ビューポートにGridのみ入れて
Banchaのサンプルを切り出しながら再整形して
CRUDの確認だけしてみます。

example.htmlについて

やることは
  • extのロード
  • Banchaの準備
ということ。
<head>
    <title>Bancha Example</title> 
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 
    <meta http-equiv="Content-Style-Type" content="text/css" /> 
    <meta http-equiv="Content-Script-Type" content="text/javascript" /> 

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

    <script type="text/javascript" src="./ext/ext-all-debug.js"></script>
    <script type="text/javascript" src="./ext/locale/ext-lang-ja.js"></script>

    <!-- include Bancha and the remote API -->
    <script type="text/javascript" src="./Bancha/js/Bancha.js"></script>
    <!-- for the github version use /Bancha/js/Bancha.js -->
    <script type="text/javascript" src="./bancha-api/models/all.js"></script>

    <!-- ExampleApp の設置 -->
    <script type="text/javascript" src="./js/src/app.js"></script>

</head> 
<body> 
</body> 
</html>
こんな感じになるのでしょうか。 404帰ってきてないこと確認しておきます。

app.jsとviwportの設置

  • addProviderの関係上、先行してExt.OnReadyが走る必要があり、
    これはBancha.OnModelReady内でExt.OnRadyしている。  
ので、OnModelReadyのコールバックでExt.applicationしてやることに
してみました。
設置については、、
* snippetsでひな形配置して
* namespace決めて
* app名決めて
* launchにalertとか設置して動作確認。
* 最初はcontrollersプロパティをコメントアウトして表示される
のを確認してもいいかもしれないです(ブランク開くと表示すら躓くのでひとつづ表示させながら)
で、
こんなかんじになると思います。

  • app.js
アプリケーションについてはローダーの設定とか重要なところになります。
これは実践開発ガイドの77ページ目のローダの説明を見ると理解が深まります。
Ext.ns('BE');

// Ext.Loader有効化
Ext.Loader.setConfig({
    enabled: true,
    paths: {
        'Ext': './ext/src',
        'BE': './js/src'
    }
});

//Bancha 初期化
Bancha.onModelReady('User',function(){

    //alert('hoge');

    // アプリケーション設定
    Ext.application({

        // Viewport自動生成
        autoCreateViewport: true,

        // アプリケーション名
        name: 'BE',

        // アプリケーションフォルダパス
        appFolder: './js/src/app',

        // 使用コントローラー定義
        controllers: [
        'Main',
        'Usergrid'
        ],

        // アプリケーション起動時イベントハンドラ
        launch: function() {

            //alert('fuga');

        }

    });
});
サーバサイドのモデルが増えたら、、
onModelReadyの第一パラメタも増やしていきます。
Bancha.onModelReady(['User','hoge1','hoge2'],function(){
みたいにね。

  • viewport.js
ビューポートはCenterにグリッドのみ設置。xtypeで指定します。
// {{{ BE.view.Viewport

Ext.define('BE.view.Viewport', {

    // {{{ requires

    requires: [
        'BE.view.Usergrid'
    ],

    // }}}
    // {{{ extend

    extend: 'Ext.container.Viewport',

    // }}}
    // {{{ layout

    layout: {
        type: 'border',
        padding: 5
    },

    // }}}
    // {{{ items

    items: [{

        region: 'center',
        //html: 'hoge'
        xtype: 'usergrid'

    }]

    // }}}

});

// }}}

Controllerの設置

上記のcontrollersをコメントインして(最初はコメントアウトしてhogeだけ表示させてた)Main,Usergridのコントローラを作成。
* MainControllerの設置をします。
* UsergridControllerの設置をします。
* これもスニペットで作成。設置のみ。ほとんどコメントアウト状態。以下、例。

  • Main.js
メインコントローラはコンポーネントからの独自イベントをディスパッチするように しておきます。
// {{{ 'BE.controller.Main',
/**
 * @class BE.controller.Main
 * メインコントローラ
 */
Ext.define('BE.controller.Main', {
    // {{{ extend

    extend : 'Ext.app.Controller',

    // }}}
    // {{{ refs インスタンス化されたViewへの参照提供 get([R]efname)
    /*
    refs: [{
        selector: 'xtype..',ref: 'localname'
    }],
    */
    /*
    // }}}
    // {{{ views クラスへの参照提供 get(Xxxx)View

    views:[
        'Views'
    ],

    // }}}
    // {{{ models クラスへの参照提供 get(Xxxx)Model

    models:[
        'Models'
    ],

    // }}}
    // {{{ stores インスタンスへの参照提供 get(Xxxx)Store

    stores: [
        'Store'
    ],

    // }}}*/
    // {{{ init

    //アプリケーション起動時にコールされる特別なメソッド
    //ApplicationのLaunch前にコールされる
    init: function() {

        //alert('hoge');
        var me = this;
        me.control(me.bindItem);

    },
    // {{{ bindItem
    bindItem: {

        // 作成
        'usergrid' : {
            create: function(){
                var me = this;
                alert('create dispatch');
                me.getController('Usergrid').onCreate();
            },
            reset: function(){
                var me = this;
                alert('reset dispatch');
                me.getController('Usergrid').onReset();
            },
            save: function(){
                var me = this;
                alert('save dispatch');
                me.getController('Usergrid').onSave();
            },
            del: function(rowIndex){
                var me = this;
                alert('save dispatch');
                me.getController('Usergrid').onDelete(rowIndex);
            }

            /*
            hoge: function(){
                var me = this;
                me.getController('Hoge').onHoge();
            },*/
        }

    }
/*
    // }}}
    // }}}
    // {{{ onXXXX

    onXXXX: function() {
        console.log('hoge');
    }
*/
    // }}}

});
// }}}

  • Usergrid.js
refとか汎用の名前にしたり、onXXとして実装と分離して抽象クラスとして
沈み込ませてそれを継承させたらすっきりするかなぁ。とおもってます。
// {{{ 'BE.controller.Usergrid',
/**
 * @class BE.controller.Usergrid
 * ユーザグリッドコントローラ
 */
Ext.define('BE.controller.Usergrid', {
    // {{{ extend

    extend : 'Ext.app.Controller',

    // }}}
    // {{{ refs インスタンス化されたViewへの参照提供 get([R]efname)

    refs: [{
        selector: 'usergrid',ref: 'crudgrid'
    }],

    // }}}
    // {{{ views クラスへの参照提供 get(Xxxx)View

    views:[
    ],

    // }}}
    // {{{ models クラスへの参照提供 get(Xxxx)Model
    // }}}
    // {{{ stores インスタンスへの参照提供 get(Xxxx)Store

    stores: [
        'User'
    ],

    // }}}
    // {{{ init

    //アプリケーション起動時にコールされる特別なメソッド
    //ApplicationのLaunch前にコールされる
    init: function() {

        var me = this;
        me.control(me.bindItem);

    },

    // }}}
    // {{{ bindItem
    bindItem: {

        // 作成
        'usergrid button[action=create]' : {
            click: function(){
                var me = this;
                alert('create');
                me.getCrudgrid().fireEvent('create');
            }
        },
        // リセット
        'usergrid button[action=reset]' : {
            click: function(){
                var me = this;
                alert('reset');
                me.getCrudgrid().fireEvent('reset');
            }
        },
        // 保存
        'usergrid button[action=save]' : {
            click: function(){
                var me = this;
                alert('save');
                me.getCrudgrid().fireEvent('save');
            }
        },
        // 削除
        'usergrid actioncolumn' : {
            click: function(view,cell,row,col,e){
                var me = this;
                alert('delete');
                me.getCrudgrid().fireEvent('del',row);
            }
        }

    },
    // }}}
    // {{{
    onCreate: function() {
        var me = this;
        me.create();
    },
    onSave: function() {
        var me = this;
        me.save();
    },
    onDelete: function(rowIndex) {
        var me = this;
        me.del(rowIndex);
    },
    onReset: function() {
        var me = this;
        me.reset();
    },
    // }}}
    // {{{
    create: function() { // scope is a config object
        var me = this,
            grid = me.getCrudgrid(),
            edit = grid.getPlugin('cellplugin'),
            store = grid.getStore(),
            model = store.getProxy().getModel(),
            rec;

        // Cancel any active editing.
        edit.cancelEdit();

        // create new entry
        rec = Ext.create(Ext.ClassManager.getName(model),{});

        // add entry
        store.insert(0, rec);

        // start editing
        edit.startEditByPosition({
            row: 0,
            column: 0
        });
    },
    save: function() { // scope is the store
        var me = this,
            valid = true,
            msg = "",
            name,
            grid = me.getCrudgrid(),
            store = grid.getStore();

        // check if all changes are valid
        store.each(function(el) {
            if(!el.isValid()) {
                valid = false;
                name = el.get('name') || el.get('title') || (el.phantom ? "New entry" : el.getId());
                msg += "<br><br><b>"+name+":</b>";
                el.validate().each(function(error) {
                    msg += "<br>&nbsp;&nbsp;&nbsp;"+error.field+" "+error.message;
                });
            }
        });

        if(!valid) {
            Ext.MessageBox.show({
                title: 'Invalid Data',
                msg: '<div style="text-align:left; padding-left:50px;">There are errors in your data:'+msg+"</div>",
                icon: Ext.MessageBox.ERROR,
                buttons: Ext.Msg.OK
            });
        } else {
            // commit create and update
            store.sync();
        }
    },
    del: function (rowIndex) {
        var me = this,
            grid = me.getCrudgrid(),
            store = grid.getStore();
        rec = store.getAt(rowIndex),
        name = Ext.getClassName(rec);

        // instantly remove vom ui
        store.remove(rec);

        // sync to server
        // for before-ExtJS 4.1 the callbacks will be ignored,
        // since they were added in 4.1
        store.sync({
            success: function(record,operation) {

                Ext.MessageBox.show({
                    title: name + ' record deleted',
                    msg: name + ' record was successfully deleted.',
                    icon: Ext.MessageBox.INFO,
                    buttons: Ext.Msg.OK
                });
            },
            failure: function(record,operation) {

                // since it couldn't be deleted, add again
                store.add(rec);

                // inform user
                Ext.MessageBox.show({
                    title: name + ' record could not be deleted',
                    msg: operation.getError() || (name + ' record could not be deleted.'),
                    icon: Ext.MessageBox.ERROR,
                    buttons: Ext.Msg.OK
                });
            }
        }); //eo store sync
    },
    reset: function() { // scope is the store
        // reject all changes
        var me = this,
            grid = me.getCrudgrid(),
            store = grid.getStore();

        store.each(function(rec) {
            if (rec.modified) {
                rec.reject();
            }
            if(rec.phantom) {
                store.remove(rec);
            }
        });
    }
});
// }}}

storeの設置

ストアの設置です。 で、いままでProxyの設定はStoreでやってたんですけど、、 Banchaが提供するModelにはすでにCakeにあわせたProxyの設定が 盛り込まれたModelを返却するのでストアはBanchaの提供するモデルを 設置するのみです。
// {{{ 'BE.store.User',
/**
 * @class BE.store.User
 * desctription
 */
Ext.define('BE.store.User', {
    // {{{ extend

    extend : 'Ext.data.Store',

    // }}}
    // {{{ model

    model: Bancha.getModel('User'),
    autoLoad: true

    // }}}

});
// }}}

modelの設置

モデルは要らないっす。 ModelはBanchaが提供してくれますので。 Bancha.getModel(‘モデル名’); で取得可能です。
または、Bancha.model.モデル名ですかね。。。

これで全部載せたかな。。。

all.jsからの返却

rpc用のメソッド一覧等の情報がちゃんとできているか見てみます。 all.jsのレスポスンスをJavaScriptBeautifierで整形してみました。 よさそうです。
Ext.ns('Bancha');
Bancha.REMOTE_API = {
    "url": "\/~banchatest\/bancha.php",
    "namespace": "Bancha.RemoteStubs",
    "type": "remoting",
    "metadata": {
        "User": {
            "idProperty": "id",
            "fields": [{
                "name": "id",
                "type": "int"
            }, {
                "name": "username",
                "type": "string"
            }, {
                "name": "password",
                "type": "string"
            }, {
                "name": "first_name",
                "type": "string"
            }, {
                "name": "last_name",
                "type": "string"
            }, {
                "name": "created",
                "type": "date",
                "dateFormat": "Y-m-d H:i:s"
            }, {
                "name": "modified",
                "type": "date",
                "dateFormat": "Y-m-d H:i:s"
            }],
            "validations": [],
            "associations": [],
            "sorters": []
            },
        "_UID": "5030537f74c0a675541393"
    },
    "actions": {
        "User": [{
            "name": "getAll",
            "len": 0
        }, {
            "name": "read",
            "len": 1
        }, {
            "name": "create",
            "len": 1
        }, {
            "name": "update",
            "len": 1
        }, {
            "name": "destroy",
            "len": 1
        }, {
            "name": "submit",
            "len": 1,
            "formHandler": true
        }],
        "Bancha": [{
            "name": "loadMetaData",
            "len": 1
        }]
        }
}
※注目すべきはxframeworkでもハマった
"formHandler": true
これ。これは.formのAPIに設定する用のハンドラにはこれをつけます。 xframeworkの時はドキュメントコメントに記載すると フレームワークがパースしてこのフラグを立ててくれてました。 超ハマったのでよく覚えてます。

実行してみる。





まとめ

で、、 プロトタイプを作成しよう!となると、
  1. UserDirの設置
  2. cake2.1.5の設置
  3. htaccessの変更
  4. Bancha0.9.5の設置(cake配下のPlugin下)
  5. bake実施(モデルとコントローラ)
  6. bakeで生成したモデルに追記
    public $actsAs = array(‘Bancha.BanchaRemotable’);
  7. Extjsの設置(cake配下app/webroot/js/配下)
  8. ExtJsMVCの設置
こんなかんじかな。
cake→banchaと夜な夜な帰宅後にやったわりには理解は思ったより早くてすっきりしてきた。 これを業務にフィードバックできればいいんだけど、
あとは、banchascaffoldだね。

感想

久々のMVCの設置、スニペットがあるのと無いのでは雲泥の差だし、
何より先の技術サイトのように一気通貫で説明があると助かるなぁ。。
と思いこのエントリも自分用に再度まとめ直したし、サンプルも作り替えた。
疲れた。。。

0 件のコメント: