Sencha Touch 2 で Ext Direct を使う (3)

Sencha Touch のすばらしいデータシステムであるStoreとExt Directを一緒に使う方法を説明します。

Direct Store (Proxy)

Ext.data.proxy.Direct を使うと、 ストアをDirectのサーバーサイド関数と関連づけられます。 使い方はAjaxProxyと非常によく似ているので、AjaxProxyの使い方に慣れている方には、 既知のことが書かれているかもしれません。

ここで説明しているプロジェクトのサンプルも、 Githubのリポジトリ に置いてあります。 このサンプルは、サーバーサイドをxFrameworkPX を使って作っています。そのプロジェクトの中のwebappディレクトリとmodulesディレクトリだけを リポジトリに上げていますので、xFrameworkPX のプロジェクトを作り、各フォルダの内容を変えてください。

まず、DirectProxyを設定してみましょう。 directFnとかapiというコンフィグにサーバーサイドのメソッドをセットします。 このProxyは、Storeに設定する事もできますし、Modelに設定する事もできます。

[js] proxy: { type: ‘direct’, directFn: ‘AM.users.getAll’, reader: { type: ‘json’, rootProperty: ‘data’, successProperty: ‘success’ } } [/js]

サーバーからのデータ読み出しだけの場合は、このようにdirectFnコンフィグを使います。 directFnコンフィグに、サーバー側のデータを返すメソッドをセットします。

[js] proxy: { type: ‘direct’, api: { create: ‘AM.users.addRec’, read: ‘AM.users.getAll’, update: ‘AM.users.updateRec’, destroy: ‘AM.users.removeRec’ }, reader: { type: ‘json’, rootProperty: ‘data’, successProperty: ‘success’ } } [/js]

読み書きできるようにして、CRUDの4つのメソッドを指定する場合は、このようにapiコンフィグを使います。 apiコンフィグには、create, read, update, destroy の4つのメンバーのオブジェクトを渡します。 それぞれのメソッドは、サーバーサイドでそれぞれの処理の時に呼び出されます。

Storeの各種のメソッドを使って、レコードの追加、更新、削除などの複数の処理を行い、 Store.sync() を呼び出すと、それをCRUDの処理に分解して、それぞれのメソッドを実行します。

CRUDのサーバーサイド

Storeと関連づけるサーバーサイドのメソッドはどういう仕様にしなければならないのか、 という点について説明します。

ここでは、データの読み書き処理には、ReaderとWriterが使われます。 ReaderとWriterはデータの形式によって違う物が用意されています。 ここでは、DirectProxy, JsonReader, JsonWriter という組み合わせの場合の話をします。

Readアクション

directFnコンフィグや、api.readに設定する、データ読み出し用のサーバーサイドメソッドはどのように実装するのでしょう。 簡単に言うと、クライアントからのパラメーターを受け取って、 その結果のデータレコードを返すということになります。

クライアントはサーバーにはどのようにパラメーターを渡すのでしょうか。 パラメーターの渡し方の基本を設定するのが、paramByHashコンフィグです。 これをtrueにすると、パラメーターは1つのオブジェクトとしてサーバーに渡されます。デフォルトはtrueです。 このコンフィグをfalseにした場合は、paramOrderなどでパラメーターの順序を決めたりしなければなりません。 このコンフィグはよほど必要の無い限り、trueのまま使います。 サーバー側で受け取る引数は1つだけなので、パラメーターの数が増えるなどの変更にも柔軟に対応できます。

Proxyのパラメータ名

  • filterParam: ‘filter’
  • sortParam: ‘sort’
  • enablePagingParams: true
  • startParam: ‘start’
  • limitParam: ‘limit’

上記の表の、値部分がパラメータ名です。 Storeのfilter, sort のようなコンフィグが指定された場合とか、 PagingToolbarを使ってグリッドをページングするような場合には、 これらのパラメーターがサーバー側に渡されます。

これらのパラメーター名は上記のコンフィグを使って変更することができますが、 既存のサーバーサイドを利用するのでもない限り変更する必要は無いでしょう。

では、これ以外のパラメータ、アプリケーション独自のパラメーターを渡す必要がある場合は、 Store.loda()メソッドの引数に params: {hoge: fuga} というオブジェクトを渡すか、 extraParamsコンフィグに設定します。

load()メソッドでパラメーターを渡す方法では、自分のアプリケーションがloadする時にはパラメーターを 渡すことができますが、Ext JS のフレームワークがデータのロードをする際には自前のパラメーターは 渡されません。 例えば、最初のloadの時に条件をパラメーターで渡したとしても、 PageingToolbarなどによりページングしていて、ページが切り替えられたときには、 フレームワークがパラメーターを作成するので、独自のパラメーターを渡す余地がありません。 そんな場合はextraParamsにセットします。 extraParamsにセットされたパラメーターはreadリクエストを送る度にパラメーターとして送信されます。 extraParamsはコンフィグですので、その値を動的に変更するには、setExtraParams()メソッドを使います。

サーバー側では、これらのパラメーターを受け取ることになりますが、 paramByHash: true の場合は、1つのオブジェクトでやってきます。

[php] public function getAll($param) { $limit = $param->limit; $page = $param->page; $start = $param->start; … [/php]

PHPの場合は受け取ったパラメーターをルーターが処理をして、StdClassオブジェクトでやってきますので、 上記のようにその中から値を取り出せます。

サーバーのメソッドの中では、そのパラメーターを使って適切な処理をし、 必要なデータを返します。

返す値は次のようなオブジェクトを返します。

[php] return array( ‘total’ => $total, ‘data’ => $r, ‘success’ => true ); } [/php]

$rには、DBから取り出したレコードの配列が入っていて、$totalにはデータの総件数が入っている、 と思ってください。 Ext.data.reader.JSONのコンフィグ設定で次のような物があります。

  • rootProperty: ‘’
    • データを格納するプロパティ名
  • successProperty: ‘success’
    • 成功フラグを格納するプロパティ名
  • totalProperty: ‘total’
    • データの全件数を格納するプロパティ名 ページングの時に必要

上記の値の部分はデフォルト値です。 先ほどの例では、rootProperty: ‘data’ と設定してあるという場合のコードです。 totalプロパティはページングをするとき以外は指定しなくても大丈夫なようです。

Create / Update アクション

Createは追加、Updateは更新ですが、 いずれも処理対象のRecord(Model)のデータがサーバーに送られます。 1つの更新で、複数件の更新や追加があった場合には、複数のレコードが配列で渡されます。

Ext.data.writer.JSONのコンフィグに allowSingle というのがあり、これがfalseの場合は、 更新されるレコードが1件の場合でも配列で渡され、 trueの場合にはレコードが1件だけの場合には配列ではなく単独のオブジェクトとして渡されます。

サーバー側では、受け取ったデータをDBに追加/更新します。 そして結果を返すのですが、何を返すのでしょうか。 それは、追加/更新した結果レコードです。 それをReaderが読める形式で返します。 Storeは、ここで返された結果通りにStoreのレコードを更新します。 Readerが読める形式で追加/更新した結果レコードを返します。 Readerが読める形式というのは、さきほどの、

[php] return array( ‘total’ => $total, ‘data’ => $r, ‘success’ => true ); [/php]

という形式です。ここで返すレコードは、追加・更新されたものだけです。 Storeはここで返された結果通りにStoreのレコードを更新します。 Createの場合は、クライアントからはIDのない(fantom)データが送られますので、 サーバー側ではそれをDBに登録して、IDをつけたレコードを返してやる必要があります。

Delete アクション

Deleteアクションでは、削除されたレコードのidだけがサーバーに送られます。 Create / Update と同様に複数ある場合は配列で送られます。 サーバー側では受け取ったデータのidを見て、該当するレコードを削除します。 リザルトには削除したidだけを返します。

with MVC

サンプルでは、directFnなどに、サーバーサイドのメソッドをそのまま指定している場合がほとんどです。

[js] directFn: Hoge.fooFunc [/js]

でもこれだと、MVCのローディングシステムと一緒に使うとうまく行きません。

ローディングシステムで Ext.DefineでStoreなりModelなりのクラス定義がされる時点では、 まだ、addProviderされていないので、関数オブジェクトができていません。 だから、生で指定するとエラーが起きます。 ですので、

[js] directFn: ‘Hoge.fooFunc’ [/js]

というように文字列で指定します。 こうするとExt.Defineの時点で、関数オブジェクトがまだ存在しなくてもエラーはでません。 実際にModelやStoreのオブジェクトがインスタンス化されるまでにaddProviderすればいいのです。

もう一点、MVCでaddProviderをどこに置くか、という問題があります。

普通にApplication.launchに置いても、うまくいきません。 launchが実行される時には、関連するオブジェクトのインスタンス化が終わっています。 とうぜん、その時にProxyのインスタンスも作られてしまいます。 だから、launchでaddProviderしても遅いのです。 Proxyのインスタンスが作られる前にaddProviderする必要があります。

Ext.onReadyの中に書くと、ApplicationがProxyをインスタンス化する前に addProviderできます。

[js] Ext.require(‘Ext.direct.*’); Ext.onReady(function() { Ext.direct.Manager.addProvider(Ext.REMOTING_API); }); Ext.application({ name: ‘AM’, controllers: [‘Users’], models: [‘User’], stores: [‘Users’], views: [‘Main’, ‘List’, ‘Edit’], launch: function() { Ext.create(‘AM.view.Main’); } }); [/js]

これでダイナミックローディングシステムを使ったMVCの場合でもエラーが出ずに実行することができます。 たぶんこれでビルドしたときにも動作すると思いますが、その点については未検証です。