ExtJS

ExtJSの勉強メモ

ExtJSに関する新しい記事は,Sunvisor Lab. ExtJS別館 に掲載されています。

ExtJSのリンク集

Ext Japan

ExtJSで楽しくRIA業務アプリ開発 (株式会社sus4)

Piz&Yuminaのプログラミング勉強ノート

EXTJS とは / EXTJS でつくる RIA の世界 - japan.internet.com コラム

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

01.ExtJS基本/設置

配置

extjsのライブラリをサーバーに配置。ここではルート直下のjsというフォルダにextというフォルダを作り,そこに必要なものを配置してみる。

ExtJS入門1 開発環境を整える | ExtJSで楽しくRIA業務アプリ開発

によると,docsとexamplesはいらないとのことなので,それを抜いて配置する。

書籍「ExtJS入門」では,ext-all.jsとext-base.jsそれとresoucesディレクトリがあればいいと書いてあるが,これはたぶんV2.1の場合でしょう。3.0以降だと,少なくともpkgsディレクトリは必要だと思う。

テンプレート?

ExtJSを利用するhtmlファイルのテンプレートを作っておくと便利かな。

Ext JS入門2 Ext JSでHello, Worldを作ってみる | ExtJSで楽しくRIA業務アプリ開発

から,まんまパクリだけど。こんな感じで。

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>Hello, World Ext JS</title>
    <!--Ext JS CSS-->
    <link rel="stylesheet" href="http://appserver/js/ext/resources/css/ext-all.css" type="text/css" />
    <!--ext js-->
    <script type="text/javascript" src="http://appserver/js/ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="http://appserver/js/ext/ext-all.js"></script>
    <script type="text/javascript">
    Ext.onReady(function(){
        alert('Hello, World!');
    });
</script>
</head>
<body>
<div class="body">
</div>
</body>
</html>

僕は,ExtJSをhtaのスクリプトから使うというのにも挑戦したいので,ライブラリを読み込むURLはhttp://をつけた完全修飾で参照しています。これなら,ローカルに置いたhtmlファイルでもExtJSが使えます。 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

02.ExtJSの開発環境

http://www.sencha.com/http://www.sencha.com/

Spketの導入

 Javascriptの開発環境はspketがいいと聞いた。それを信じてspket導入する。

導入方法は「Javascript向けIDE「Spket IDE」の導入」を参照した。

この記事のとおり,spketのexe版を導入してみた。あとでpleiadesを入れて日本語化もでき,とても簡単に導入できた。

ExtJSでのコード補完の設定もしておく。参考にしたのは「Eclipse + Spket + ExtJS でコード補完をしてみる」。でもExtJSのバージョンがちょっと違うみたい。3.1.1の場合は,おそよ次のとおりの手順かな?

  1. ウィンドウ > 設定 で設定ダイアログを開く
  2. 左ペインからSpket > JavaScript Profiles を選択
  3. 右ペインの「新規」ボタンをクリック
  4. 適当な名前をつける(ExtJSなど)
  5. 「ライブラリーの追加」ボタンをクリック
  6. 「ライブラリー」はExtJSを選択してOKをクリック。
  7. 追加したライブラリーExtJSを選択して,「ファイルの追加」をクリック。
  8. ExtJSのフォルダのルートにある,ext.jsb2を選択して,「開く」をクリック。
  9. ExtAllにチェックが入っているのを確認。

 この手順のウチ2のところでJavaScript Profilesの画面がうまく表示できなかった。ウィンドウをリサイズしたりするとやっと表示されたり,変な動きをする。致命的なのはプロジェクトの新規作成ダイアログ。なにも表示されないのでプロジェクトを作れない。使っているOSは,Win7のx64。

spket.exeは,Eclipseの3.2あたりをベースにしているみたい。そしてどうもEclipseの古いバージョンでは,x64においてレイアウトが崩れるバグがあるらしい。

そこで,Eclipseの最新版を入れて,spketプラグインを入れるという方法を試す。

  1. Pleiades All in One が楽なので, まずそれをダウンロードして展開。
  2. 次に,spketのプラグインをダウンロードして展開。
  3. プラグインをEclipseのフォルダにコピー

これでなんとか動作した。こちらの環境でもコード補完の設定をしようとしたら,すでに設定されている。spket.exeで設定したのが残っているみたい。設定情報はレジストリとかに保存されるのだろうか。まぁいいや。設定情報はワークスペースに保存されるのでした。おはずかしい。

Ext Designer の導入

http://www.sencha.com/ からExt Designer をダウンロードしてセットアップ。何の苦労もいらない。

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

03. Ext Designer で作成されるファイル

Ext Designer で作成されるファイル

Ext Designerで,画面上にいろいろ配置してプロジェクトを保存した後に,「Export Project」を実行するとソースファイルが生成されます。ファイルは,トップレベルのコンポーネントごとにhogehoge.jsというファイルとhogehoge.ui.jsという二つのファイルというようになります。

またデータストアの場合はもデータストアごとに一つのファイルができます。

そのほかに,

xds_includeOrder.txt ファイルのインクルード順を記述してあるテキストファイル。この中身を実際に利用するhtmlファイルに挿入すると良い。
xds_index.html  作成した画面を表示するhtml
xds_index.js  作成した画面を表示させるonReady定義が書いてある。

生成されたファイルの中で使うのは,上記の3つをのぞいたものとなるでしょう。

画面のデザインは開発途中でも何度も変更になると思われます。ですので,Ext Designer で生成されたソースを手メンテしないようにしなければなりません。画面デザイン以外の部分は,別のファイルに記述して,xds_includeOrder.txtにあるscript文に続いて読み込ませるということになると思います。

hogehoge.jsというファイルとhogehoge.ui.jsにわかれていたのは,これのためでした。最初にこの二つのファイルを生成してプロジェクトにインポートした後は,hogehoge.jsというファイルは変更してもよいのです。hogehoge.ui.jsは,UIに変更があるたびに再度インポートするわけです。オブジェクトにイベントハンドラを追加する場合などは,hogehoge.jsを修正して行います。

イベントハンドラの記述

ボタンが押されたときなどのイベントハンドラは,各種サンプルを見るとボタン生成の段階でオブジェクトリテラルに匿名関数を記述している例がほとんどですが,上記の理由でファイルを編集するわけにはいきませんから,別ファイルで追加することになります。上記の理由で,hogehoge.jsを編集して記述します。

このあたりのことは,ブログ記事に書きましたが,まちがいでした。

別件ですが,コンポーネントに値を設定する場合もgetCmpで得たオブジェクトに対してsetValueメソッドでセットします。

Ext.getCmp('btnTest').handler = function () {
        Ext.Msg.alert('test','test');
        Ext.getCmp('txtTest').setValue('aaaaa');
    }
    Ext.getCmp('txtTest').on('change', function () {
        Ext.Msg.alert('test','変更されたよ');
    });

Ext.getCmpというメソッドでコンポーネントのオブジェクトを参照できるんですね。これは実は,Ext.ComponentMgr.getの別名なんだそうです。

これも,どうも間違いに近いです。Ext.getCmpを使うことも可能なのですが,もっと簡単にできます。やっぱりちゃんと勉強しないといけませんね。hogehoge.jsのにイベントハンドラの定義を書きます。その際にはautoRefの設定がちゃんとされていれば,次のように書くことができます。

MyForm = Ext.extend(MyFormUi, {
    initComponent: function() {
        MyForm.superclass.initComponent.call(this);
        this.submitBtn.on('click', this.onSubmitBtnClick, this);
        this.cancelBtn.on('click', this.onCancelBtnClick, this);
    },
    onSubmitBtnClick: function() {
        // your implementation here!
    }
    onCancelBtnClick: function() {
        // your implementation here!
    }
});

非常にわかりやすいですよね。

データストア

データストアの場合はどうでしょうか。コンボボックスにセットするアイテムで,リストの内容が固定的なものなら,直接Designerのプロパティに設定することもできます。ArrayStoreを追加して,dataプロパティに配列のリテラルを書けばOKです。

べつな配列に入れておいて,それをセットする場合は,lodaDataメソッドを使いますが,その場合はデータストアを参照するために,Ext.StoreMgr.get を使います。

var myData = [
    [1, 'Office 2003'],
    [2, 'Office 2007'],
    [3, 'Office 2010']
];
Ext.StoreMgr.get('MyStore').loadData(myData);

こういう感じです。 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

04. パネルの中身をセットする

 パネルの内容を動的にセットするには,Ext.Templateを使うのがExtJS流みたいです。

参考サイト

テンプレートとなるhtml文をセットしてテンプレートオブジェクトを生成。そのhtmlは,ページ中の要素からも取り出せるし,文字列で与えても良い。

Ext.onReady(function() {
    Ext.QuickTips.init();
    var cmp1 = new MyViewport({
        renderTo: Ext.getBody()
    });
    var sTemp = "<div>{contents}</div>";
    var oTemp = new Ext.Template(sTemp);
    Ext.getCmp("btnShow").handler = 
        function() {
            oTemp.overwrite(
                Ext.getCmp("pnlCanvas").body, 
                {
                    'contents':
                    Ext.getCmp("txtHtml").getRawValue()
                }
            );
        }
    ;

    cmp1.show();
});

そのテンプレートオブジェクトのapplyメソッドでテンプレートから生成したhtmlを得ることができるし,overwriteメソッドを使うとエレメントを書き換えることができる。そのエレメントにpanelオブジェクトのbodyを渡してやれば,パネルの中身を書き換えられるということです。

  1. 6行目でテンプレートの元になるhtml文を定義。
  2. 7行目でTemplateオブジェクトを生成。
  3. 9行目からはbtnShowというボタンのイベントハンドラで,txtHtmlというHtmlEditorコンポーネントで入力された値をpnlCanvasというパネルに表示しています。

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

05.Ext Designer 入門

Senchaのサイトにある, Getting Started with Designer (PDF) を読みました。英語なので読み飛ばしていたのですが,やっぱりちゃんと読んでおくべきだと思いました。

それで,その文書を翻訳してみました。

しかしちゃんとマニュアルってのは読まなきゃいけませんね。自分が作ったシステムを使う人には「マニュアル読んでね」っていつも言ってるのに自分はなにを過信してかマニュアル読まずにExt Designerを使っていました。そんな状況で  Getting Started with Designer (PDF) を読んだら,「ここってどうするんだろう」ってことがちゃんと書いてありました。

最も重要だったのは,Ext Designerで作成ししたものにイベント処理を追加するときのやり方のことでした。僕は勝手にDesignerからエクスポートしたファイルを編集してはいけないと思い込んでいたので,自前でそこをなんとかする方法を考えないといけないと思っていたのですが, Getting Started with Designerde (PDF) を読んだらちゃんと書いてありました。そういうときは.jsファイルを編集しなさいって。

Designerにコードをエクスポートさせると,hogahoga.ui.jsというのとhogehogejsという二つのファイルが出来るのはなぜなんだろうとは思っていたんですよ。でもhogehoge.jsは編集してもかまへんとは思わなかったんです。でも,ちゃんと Getting Started with Designer (PDF) に書いてあったんです。で,実際に試してみると,hogehoge.jsは出力されたあとに編集しても,次にDesignerからエクスポートをしても上書きされないんです。これはスゴイですよ。ちゃんとマニュアルは読むべきです。

今回,Designer 入門(日本語版)をやってみましたが,Designerのページにはあと4つほどのpdfドキュメントがあります。時間と僕の能力が許せばそれらの日本語版も作ってみたいと思います。

ということで,マニュアルを翻訳しました。全部で5つの文書になりました。

06.Ext Designerのマニュアル文書

にすべてを保存しましたのでよかったら読んでやってください。

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

06.Ext Designerのマニュアル文書

Ext Designerの操作を学んでいて,結局マニュアルをちゃんと読むべきだという結論に達し,一念発起して英文のマニュアルを読みました。

http://www.sencha.com/products/designer/ のページに, 

という5つの文書があり,それぞれに有益なことが書かれています。Designerで作業するための文書ですが,ExtJSの入門書のひとつとしても役立ちます。英文をすらすら読める人間ではないので,辞書やオンライン翻訳などを駆使して読んでいくのですが,読んだそばから忘れます。(^^; ですので翻訳して記録しなければなりませんでした。で,せっかく記録したので公開します。下手な翻訳ですが,お役に立てたらうれしいです。

 

これらの文書で学習したこと

  1. Designerが生成するソースコードがhogehoge.jsとhogehoge.ui.jsに分かれているのは,生成後にhogehoge.jsを編集してイベントハンドラを書くためだったというのがわかったことが最大の収穫でした。これらの文書に書いてあるように,hogehoge.jsは一度生成されたら二度と上書きされません。安心して自分のコードを書くことができます。
  2. DesignerのComponent ConfigインスペクタでautoRef属性を設定すると,Javascriptコード上でコンポーネントのオブジェクトを参照しやすくなるというのも重要なことでした。autoRef(ExtJSではref属性にセットされる)をセットしたら,Javascriptの中で,this.hogehogeFieldのように簡単に参照できるのです。これを知らなかったら大変な苦労をするところでした。
  3. Designerでは,複数のトップレベルのコンポーネントを設定できます。プロジェクトで使うViewportやWindowを複数作ってエクスポートするとそれぞれが別のクラスファイルになってくれます。ひとつのプロジェクトで複数の画面を使うときにはトップレベルのコンポーネントを複数作ります。
  4. その他に,部品をトップレベルのコンポーネントとして作成する方法があります。画面の部品を作り,それを複数のフォームから使うことができます。これによりよく似た画面を沢山作ってしまうことが避けられます。

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

07.DirectStore を xFrameworkPX とともに使う

DirectStore/EditorGridPanel を使ってアプリケーションを作る時の基本的なことを書き留めておきたいと思います。サーバーサイドは,xFrameworkPX を使います。 

サーバーサイド

xFrameworkPXでは,ExtDirect コントローラーとモジュールをつくります。通常の xFrameworkPX の使い方については,ドキュメントに詳しいので,ここでは ExtDirect に関することだけを書きます。

コントローラー

ファイル名:webapp/.extdirect.php

<?php
class extdirect extends xFrameworkPX_Controller_ExtDirect {

    public $direct = array(
        'namespace' => 'Ext.myApp'
    );

    public $modules = array(
        'testdata' => array(
            'conn' => 'default'
        )
    );
}

xFrameworkPX_Controller_ExtDirect を継承したクラスを作ります。このクラスの execute メソッドでは,ExtDirect の API を返します。

4行目で$direct 配列にオプションを設定しています。ここでは namespace に Ext.myApp と設定しています。namespace に何も設定しない場合,サーバーサイドのメソッドは モジュール名.メソッド名 の形式になりますが,namespace を設定すると namespace.モジュール名.メソッド名 の形式になります。個人的には namespace を設定したほうがソースが読みやすくなると思います。

$direct で設定できる他のオプションは次の通りです。

オプション 初期値 備考
url extdirect.html APIのURL。これを変える時は,コントローラーのクラス名とファイル名も一致させる必要があると思います。(きっと)
type remoting  
descriptor Ext.app.REMOTING_API  
namespace (設定なし)  

モジュール

<?php
class testdata extends xFrameworkPX_Model
{
    var $usetable = 'tblData';

    public function getStore()
    {
        $result = $this->get(
            'all',
            array(
                'fields' => array(
                    'id','val1','val2','val3','val4','val5','val6','val7',
                    'val8','val9','val10','val11','val12','val13'
                )
            )
        );
        return array(
            'success' => true,
            'data' => $result,
            'total' => count($result)
        );
    }
}

テストデータのテーブルですので,DBのテーブルを読んできて,それを全部返しています。本当は抽出条件などを指定するところですが,まぁサンプルということで。最後に取得したデータを連想配列にセットしてます。これはDirectStoreの形式に合わせています。

クライアントサイド

Ext.Designerで,StoreとUI部分を作ります。出力されたコードは次のような感じ。

ストア

ファイル名:MyStore.js

MyStore = Ext.extend(Ext.data.DirectStore, {
    constructor: function(cfg) {
        cfg = cfg || {};
        MyStore.superclass.constructor.call(this, Ext.apply({
            storeId: 'MyStore',
            directFn: Ext.myApp.testdata.getStore,
            root: 'data',
            fields: [
                {
                    name: 'id',
                    mapping: 'id',
                    type: 'int'
                },
                {
                    name: 'val1',
                    mapping: 'val1',
                    type: 'int'
                },

                       :
                       :

                {
                    name: 'val13',
                    mapping: 'val13',
                    type: 'int'
                }
            ]
        }, cfg));
    }
});
new MyStore();

directFn のところに namespace.モジュール名.メソッド名 で先程作ったメソッドを設定(Ext.myApp.testdata.getStore)します。root コンフィグには,モジュールでデータをセットした配列のキー(data)をセットします。

UI部分

ファイル名:MyViewport.ui.js

MyViewportUi = Ext.extend(Ext.Viewport, {
    layout: 'border',
    initComponent: function() {
        this.items = [
            {
                xtype: 'editorgrid',
                title: 'My Grid',
                store: 'MyStore',
                region: 'center',
                height: 410,
                ref: 'grdData',
                columns: [
                    {
                        xtype: 'gridcolumn',
                        header: 'id',
                        dataIndex: 'id',
                        sortable: true,
                        width: 100,
                        editor: {
                            xtype: 'textfield'
                        }
                    },
                    {
                        xtype: 'gridcolumn',
                        header: 'val1',
                        dataIndex: 'val1',
                        sortable: true,
                        width: 100,
                        editor: {
                            xtype: 'textfield'
                        }
                    },

                             :
                             :

                    {
                        xtype: 'gridcolumn',
                        header: 'val13',
                        dataIndex: 'val13',
                        sortable: true,
                        width: 100,
                        editor: {
                            xtype: 'textfield'
                        }
                    }
                ]
            }
        ];
        MyViewportUi.superclass.initComponent.call(this);
    }
});

自動生成されたままです。というか Designer で出力された ui.js のファイルは触らないのが約束です。 

ファイル名:MyViewport.js

Ext.Direct.addProvider(Ext.app.REMOTING_API);

MyViewport = Ext.extend(MyViewportUi, {
    initComponent: function() {
        MyViewport.superclass.initComponent.call(this);
        this.on('beforerender', this.onBeforeRender, this);
    },
    onBeforeRender: function () {
        var s = this.grdData.getStore();
        s.load();
    }
}

このファイルは触っても大丈夫です。ここにイベントハンドラなどを書きます。ViewPort の beforerender イベントで,Storeのload()メソッドをコールしています。

起動部分

ファイル名:index.js

Ext.onReady(function() {
    Ext.QuickTips.init();

    var cmp1 = new MyViewport({
        renderTo: Ext.getBody()
    });
    cmp1.show();
});

index.js に Ext.onReady を書きます。

ファイル名:index.html

<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
    <title>project.xds</title>
    <link rel="stylesheet" type="text/css" href="extjs/resources/css/ext-all.css"/>
</head>
<body>
    <script type="text/javascript" src="extjs/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="extjs/ext-all-debug.js"></script>
    <!-- Ext Direct API -->
    <script type="text/javascript" src="extdirect.html"></script>
    <!-- /Ext Direct API -->
    <script type="text/javascript" src="js/MyViewport.ui.js"></script>
    <script type="text/javascript" src="js/MyViewport.js"></script>
    <script type="text/javascript" src="js/MyStore.js"></script>
    <script type="text/javascript" src="js/index.js"></script>
</body>
</html>

uiコンポーネントを読み込む前に Ext.Direct API を読み込んでいます。 

MyViewport.js の1行目に Ext.Direct.addProvider(Ext.app.REMOTING_API); を記述しています。この行の記述場所を間違うとStoreの作成に失敗してしまいます。

失敗例:Ext.onReadyの中でコールする。

index.jsの前にMyStore.jsを読み込んでいます。Designerの出力するコードでは,MyStore.jsの最後で,Storeがnewされています。その時点でExt.Directプロバイダが追加されてなければならないのに,それが処理されていないのでエラーになります。

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

08.DirectStore でサーバー更新をかける

2010/03/25 更新 

DirectStore でサーバー側のデータを読むことができるのは知っていました。が,更新をかける方法がわからず,Storeの中の更新データを取り出すメソッドを読んで,そのデータをExt.Directの関数をコールすることでサーバー側に渡していました。

でも,ちゃんと設定すれば,DirectStore の save() メソッドをコールするだけで,サーバーに更新をかけることができます。

Extjs/SenchaTouch Perfect Day #002 に参加して,@kotsutsumi さんのお話を聞いてから,泊まったホテルと帰りの新幹線の中でExtJSのソースを読み直し,DirectStore の動作について調べてみました。その結果,DirectStore でデータを扱う場合の方法の全体像がわかってきたので,ここにメモします。

DirectStore の定義

DirectStoreを普通につくるとWriterがありません。更新作業をするためには Writer が必要ですのでJsonWriterを追加してやる必要があります。

var writer = new Ext.data.JsonWriter({
    encode: false,
    writeAllFields: false // write all fields, not just those that changed
});

MyStore = Ext.extend(Ext.data.DirectStore, {
    constructor: function(cfg) {
        cfg = cfg || {};
        MyStore.superclass.constructor.call(this, Ext.apply({
            storeId: 'MyStore',
            writer: writer,

 

この例では別に宣言してそれをコンフィグに設定していますが,もちろんコンフィグの中でオブジェクトリテラルで定義しても大丈夫です。

DirectProxy の場合は directFn にサーバー側のメソッドをセットすると思っていたのですが,それはやめて,api コンフィグを設定します。

            api: {
                create: Ext.myApp.testdata.create,
                update: Ext.myApp.testdata.update,
                read: Ext.myApp.testdata.getStore,
                destroy: Ext.myApp.testdata.getStore
            },

サーバー側に4つのメソッドを用意してそれを api にセットします。api の create とか update とかをアクションと言いまして,4つ合わせて CRUD と言います。directFn にセットした場合は,CRUD すべてのアクションに directFn にセットしたメソッドがコールされます。でもこれは現実的ではありません。普通はそれぞれのアクションごとにメソッドを用意することになると思いますので, api にセットします。

CRUDのそれぞれの動きについて

Store の save() メソッドがコールされたときに,Store の更新内容に応じて,create, update ,destroy といった書き込み系のアクションが実行されます。これらは複数実行されることがあります。これらの書き込み系アクションの場合に,サーバーに渡される引数は一つのJsonデータです。サーバー側は受け取ったパラメータをJsonデータとして受け取り,create ならDBに insert,update ならDBを update など,適切に処理するようにします。(このようにサーバーに渡されるのはデータの入ったJsonオブジェクトだけなので,サーバー側でどんなアクションが発生したのかを知るには,メソッドを別なものにしなければなりません。ですから,やはり directFn だけを指定するのは,read しか使わない時ってことになります。)

update アクションが実行された時にサーバーに渡されるJsonデータは,変更のあったレコードのみです。受け取ったサーバーは,そのデータでDBを update します。Writer オブジェクトの writeAllFields コンフィグを true にした場合は,すべてのフィールドがセットされますが,false にすると,変更のあったフィールドとIDフィールドのデータのみがセットされます。

ひとつだけ違うのが読み込み系の read アクションです。read の場合は paramOrder にセットしてあるとおりのパラメータが渡されます。paramOrder は read アクションのための設定で,他のアクションでは使われません。サーバー側の関数がとる引数を paramOrder で指定します。サーバー側がとるパラメータがない(パラメータが0個の)場合は,paramOrder は無視されます。

ここまでの動きを理解して,サーバー側のメソッドを実装します。そうすれば,Store の save() メソッドをコールすれば,DBが更新されますので,クライアント側のコードを書く量がぐっと減ります。

更新系APIで渡されるデータについて

Create, Update, Delete といった更新系の API でサーバーに渡されるデータはどのような形式なのでしょうか。 以下,item とあるのは,root 要素が item である場合です。root 要素が違う名称の場合はそれに置き換えて読んでください。

Delete に渡されるデータ

1件だけの場合: { item: <id> }

複数の場合: { item: [<id1>, <id2>, ...] }

渡されたオブジェクトの item 要素に,削除されたレコードの id がセットされて渡されます。削除されたデータが1件の場合は id そのものが,複数の場合は配列でセットされます。

Create に渡されるデータ

1件だけの場合: { item: < 追加するデータオブジェクト> }

複数の場合] { item: [ < 追加するデータオブジェクト1>, < 追加するデータオブジェクト2>...] }

渡されたオブジェクトの item 要素に,追加されたレコードのオブジェクトがセットされて渡されます。追加されたデータが1件の場合は一つのオブジェクトが,複数の場合にはオブジェクトの配列がセットされます。オブジェクトは,フィールド名: 値 という構成になっています。

Update に渡されるデータ

1件だけの場合: { item: < 追加するデータオブジェクト> }

複数の場合] { item: [ < 追加するデータオブジェクト1>, < 追加するデータオブジェクト2>...] }

渡されたオブジェクトの item 要素に,更新されたレコードのオブジェクトがセットされて渡されます。更新されたデータが1件の場合は一つのオブジェクトが,複数の場合にはオブジェクトの配列がセットされます。オブジェクトは,フィールド名: 値 という構成になっています。Writer の writeAllFields が true の場合には,ストアの全てのフィールドのデータが渡されますが,writeAllFields が false の場合は,変更があったフィールドのデータだけが渡されます。

更新系APIの戻り値について

Create, Update, Delete といった更新系の API では,決まった形式でデータを返してやらないと,ExtJS側でエラーが発生します。これがわかるまで結構ハマりました。

基本としては,

{ success: true, items:[{<処理したレコード1>}, {<処理したレコード2>}, ...] }

という形式のオブジェクトを返します。success 要素は,失敗した場合にはもちろん false をセットしますが,ここに true を返した場合は item 要素も返します。item 要素は, クライアントから渡すデータと同じで,root 要素の名前になります。root 要素が違う名称の場合はそれに置き換えて読んでください。この item 要素は,処理したレコードを返します。Create や Update では,渡されたデータを元にして配列にして返せばいいですし,Delete の場合は,渡されたデータは id のみなので, { id: $id } というようにオブジェクトにして返します。

サンプルコード

サンプルコードを github に用意しました。(恥ずかしいけど)

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。

09.サーバーサイドでDirectStoreの更新処理を書く

08. で DirectStore でのサーバー更新について調べましたが,それでは実際にサーバー側のルーチンはどう書いたらいいのでしょうか。ここでは,PHPとxFrameworkPXを使った場合を例示します。xFrameworkPXでは,ExtDirectコントローラーを使えば,モジュールのメソッドがそのままExtDirectで使えるという便利な機能があります。ここでは,そのモジュールのメソッドの記述例を示してExtDirectでのサーバー側の実装を示します。ただ,他のフレームワークを使った場合でも,API周りの処理に違いがあるとは思いますが,実際に処理するメソッド自体は大同小異だと思いますので,参考にはなると思います。

クライアント側 JavaScript

DirectStore の定義

次のような DirectStore を定義して,このストアの save メソッドをコールしたときに,ちゃんとサーバー側で更新がかかるようにします。例では autoSave を false にして自動保存されないようにしていますが,自動保存する場合でもほぼ同じです。この例では,保存するときの beforewrite イベントでローディングマスクを表示する処理を加えて, write イベントでマスクを解除しています。API コンフィグで指定しているのは,ExtDirect で公開されているメソッドです。Ext.remote というネームスペースに定義されるようになっています。CRUDそれぞれのメソッドが定義されています。

Create = insertDataメソッド
Read = readDataメソッド
Update = updateData メソッド
Delete = deleteData メソッド

Store へのレコードの追加

ここでは参考までに Store にデータを追加するコードを紹介します。

var newRec;

newRec = new me.store.recordType(obj, null);
me.store.add( newRec );

このように,recordType でレコードを作り,add メソッドで追加します。上記の例ではobjに追加したいレコードのデータが入っているという前提です。レコードの id プロパティが空の時に ExtJS はそれが追加されたレコードであると判断します。

サーバー側 PHP(xFrameworkPX)

次にサーバー側のコードを書いていきます。

ExtDirect コントローラー

.extdirect.php を webapp フォルダに作成します。

class extdirect extends xFrameworkPX_Controller_ExtDirect
{

    public $direct = array(
        'namespace' => 'Ext.remote'
    );
    public $modules = array(
        'employee' => array( 'conn' => 'default' )
    );

}

ネームスペースを設定して,利用するモジュール(employee)をuseしています。これだけで xFrameworkPX がいろいろと世話をしてくれるので, employee モジュールのメソッドを ExtDirect から利用可能になります。

employee モジュール

class employee extends xFrameworkPX_Model
{

    public $usetable = 'employee';
    
    // メソッドを記述

}

xFrameworkPX でのモジュールは,modules ディレクトリに配置します。 

readData メソッド

データを読み出すための readData メソッドを実装します。

public function readData()
    {

        $fields = array(
            'id',
            'name_kanji',
            'name_kana',
            'birth_date'
        );
        $order = array(
            'id'
        );

        $result = $this->get(
            'all',
            array(
                'fields' => $fields,
                'order' => $order
            )
        );

        return array(
            'success' => true,
            'total' => count( $result ),
            'items' => $result
        );

    }

データベースから従業員データをとってきて,その一覧を返しています。返却するデータは最後の return 文のような形式で返します。

この中の items というキー名は任意ですが,このキー名を Store の root コンフィグにセットしなければなりません。他のキー名にした場合には,Store の root コンフィグにその名前をセットします。

deleteData メソッド

destroy アクションが発行された場合に実行されます。この場合,サーバに渡されるオブジェクトの items プロパティに削除すべきレコードの id が渡されます。削除するレコードが複数ある場合には items は配列になります。この例での items となっている要素ですが,この items というキーは前述の通り, root コンフィグに設定されている値になります。root コンフィグの値が違っていればこのキー名も変わります。(この点については他のメソッドでも同様です)

実際の deleteData メソッドのコードです。

    public function deleteData( $pData )
    {

        $data = $pData->items;
        if( ! is_array($data) ){
            $data = array($data);
        }

        $result = true;

        foreach( $data as $id ){
            $result = $result && $this->_deleteOne( $id );
        }
        return array(
            'success' => $result,
            'items' => $data
        );

    }

    private function _deleteOne( $id )
    {

        $where = array(
            'id = :id'
        );
        $bind = array(
            'id' => $id
        );
        $result = $this->delete(
            array(
                'where' => $where,
                'bind' => $bind
            )
        );
        return $result;

    }

サーバーに渡されるのはJSONのデータですが,PHPで受け取ったときには StdObject型のデータになります。配列の場合は配列で受け取れるのですが,オブジェクトの場合は StdObject のデータが渡されます。そこで最初に渡されたデータを配列にキャストしています。

Store の中で一つのレコードが削除された場合と,複数のレコードが削除された場合で,渡されるデータが違います。複数の場合のみデータは配列になっています。渡されたデータが配列であるかどうか判断して,配列でない場合は要素が一つの配列に格納してます。そしてその後,配列の各要素を引数にして _deleteOne メソッドをコールしています。

このメソッドの戻り値ですが,次のような内容を返す必要があります。

{ success: <flag>, items: [ {id : <id1>}, {id: <id2>}, ....] }

success プロパティには実行結果をセットします。true の場合は成功,false の場合は失敗ですね。成功した場合には,items (例によって root コンフィグの値によって変わります)プロパティに,削除されたレコードの id フィールドの値をプロパティに持つオブジェクトの配列をセットします。戻り値の方は1件の削除でも配列にする必要があります。_deleteOne メソッドの最後の方でこの1件ごとのオブジェクトを作成して返しています。deleteData メソッドの最後では,それらの結果の配列と success プロパティをつけて返しています。

渡されるオブジェクトと返すオブジェクトの形式は,update や insert でもほぼ同じですので,よく覚えておいてください。

updateData メソッド

update アクションの時には,更新されたレコードのデータが渡されます。ここでも1レコードの場合は1件のオブジェクトが,複数レコードの場合はオブジェクトの配列が渡されます。Store の writeAllFields コンフィグの値によって,渡されるオブジェクトの内容が変わります。この値が true の場合は,すべてのフィールドが渡されますし,この値が false の場合はキーのフィールドと変更があったフィールドだけが渡されます。

public function updateData( $pData )
    {

        $data = $pData->items;
        if( ! is_array($data) ){
            $data = array($data);
        }
        $result = true;
        foreach( $data as $rec ){
            $result = $result && $this->_updateOne( $rec );
        }

        return array(
            'success' => $result,
            'items' => $data
        );

    }

    private function _updateOne( $pRec )
    {

        $rec = (array)$pRec;

        $fields = array(
            'name_kanji',
            'name_kana',
            'birth_date'
        );
        $values = array(
            ':name_kanji',
            ':name_kana',
            ':birth_date'
        );
        $where = array(
            'id = :id'
        );

        return $this->update(
            array(
                'field' => $fields,
                'value' => $values,
                'where' => $where,
                'bind' => $rec
            )
        );

    }

このメソッドでは渡されたデータをひもといて update メソッドでレコードの更新をかけています。ここでも適切な値を返さなければなりません。やはり success プロパティと items プロパティが必要です。

ToDo:(items が id さえあればいいのかどうか確認。writeAllFields が false の場合も検証)

insertData メソッド

create アクションの時には,追加されたレコードのデータが渡されます。ここでも1レコードの場合は1件のオブジェクトが,複数レコードの場合はオブジェクトの配列が渡されます。戻り値も同様です。

public function addData( $pData )
    {

        $data = $pData->items;
        if( ! is_array($data) ){
            $data = array($data);
        }

        $result = true;
        $rData = array();
        foreach( $data as $rec ){
            $rRec = (array)$rec;
            $id = $this->_addOne( $rRec );
            $rRec['id'] = $id;
            $rData[] = $rRec;
        }
        return array(
            'success' => $result,
            'items' => $rData
        );

    }

    private function _addOne( $rec )
    {

        $fields = array(
            'name_kanji',
            'name_kana',
            'birth_date'
        );
        $values = array(
            ':name_kanji',
            ':name_kana',
            ':birth_date'
        );
        // 挿入実行
        $result = $this->insert(
            array(
                 'field' => $fields,
                 'value' => $values,
                 'bind' => $rec
            )
        );
        $id = $this->lastId();

        return $id;

    }

このメソッドでは,id フィールドは auto increment になっているという前提です。

ここのコードは,記事に書くためにちょっと短くしています。こちらのGitHubリポジトリですべてのソースをご覧になれます。

 

トラックバック


URL から "-MoIyadayo" を削除してトラックバックを送信してください。
トラックバックは承認後に表示されます。