2013年1月15日火曜日

ExtJS 覚書 #2 validation

Ext.data.Modelを使ったクライアントサイドバリデーション

データモデルにvalidationの定義を含めることができる。
Ext.define('User', {
    extend: 'Ext.data.Model',
    fields: [
        {name: 'name',     type: 'string'},
        {name: 'age',      type: 'int'},
        {name: 'phone',    type: 'string'},
        {name: 'gender',   type: 'string'},
        {name: 'username', type: 'string'},
        {name: 'alive',    type: 'boolean', defaultValue: true}
    ],
    validations: [
        {type: 'presence',  field: 'age'},
        {type: 'length',    field: 'name',     min: 2},
        {type: 'inclusion', field: 'gender',   list: ['Male', 'Female']},
        {type: 'exclusion', field: 'username', list: ['Admin', 'Operator']},
        {type: 'format',    field: 'username', matcher: /([a-z]+)[0-9]{2,3}/}
    ]
});

バリデートはExt.data.Modelのvalidate()で行う。
結果は、Ext.data.Errorsが返される。

var errors = instance.validate();

サーバサイドバリデーション

サーバサイドのバリデーションの結果をExt.data.Model.validate()と同じ形で得るためには、データモ
デルのproxyにreaderを正しく設定する必要がある。
 Ext.define('Employee', {
    extend : 'Ext.data.Model',
    idProperty : 'id',
    fields : [
        { name : 'id', type : 'string' },
        { name : 'firstName', type : 'string' },
        { name : 'lastName', type : 'string' },
        { name : 'gender', type : 'string' }
    ],
    proxy : {
        url : "/コンテキストルート/employeeService",
        type : "rest",
        reader : {
            type : 'json',
            messageProperty : 'errors'
        }
    }
});

このデータモデルのsave()メソッドに対して、サーバ側バリデーションを返す場合、以下のようなJSONデータをサーバは返さなければならない。
{
    'success' : false,
    'errors' : { 'id' : 'Error Message1', 'firstName' : 'Error Message 2', 'lastName' : 'Error Message3' }
}

readerのmessagePropertyの値と、JSONデータのエラーのプロパティを一致させること。
バリデーション結果は、モデルのload(), save(), destroy()メソッドのsuccess, failureコールバックの引数 operation (ext.data.Operation) から得られる。
var emp = Ext.ModelManager.getModel('Employee');
emp.set('123', 'Fidel', 'Castro', 'Male');
emp.save({
    waitMsg : 'Saving...',
    success : function(operation){ },
    failure : function(operation){
        var errors = operation.getError();
    }
});

validationの結果をformに適用する
formのmarkInvalid()を使ってvalidationの結果をformに適用する事が出来る。
var errors = instance.validate();
form.markInvalid(errors);

jax-rs(jersey)覚書 #4 自前のシリアライザー : 日付フォーマットを指定する

jax-rsはオリコウさんで、pojoがそのままjsonになって飛んでいきます。
ところが、java.uti.Dateのフィールドがイケてない形で行ってしまいました。
その結果、Ext JSのグリッドやformのdate型のフィールドに表示されず。
jsonにシリアライズするときにフォーマットの指定できないかしらと調べてみた。

まず、org.codehaus.jackson.JsonGenerator を継承したシリアライザーを作る。
serializeというabstractのメソッドを実装してあげればよい。
Date型を yyyy/MM/DD にするならこんな感じ。

import java.io.IOException;
import java.text.SimpleDateFormat;

import org.codehaus.jackson.JsonGenerator;
import org.codehaus.jackson.JsonProcessingException;
import org.codehaus.jackson.map.JsonSerializer;
import org.codehaus.jackson.map.SerializerProvider;

public class MyDateSerializer extends JsonSerializer<Object> {

    @Override
    public void serialize(Object value, JsonGenerator gen,
   SerializerProvider provider) throws IOException,
   JsonProcessingException {

        SimpleDateFormat formatter = new SimpleDateFormat("yyyy/MM/dd");
        String formattedDate = formatter.format(value);

        gen.writeString(formattedDate);
  
    }
}

そして、jsonに変身するクラスの、作ったシリアライザーを適用したいフィールドを @JsonSerialize アノテーションで修飾する
public class Person {
 
    private String firstName;
    private String lastName
    
    @JsonSerialize(using = MyDateSerializer.class)
    private Date birthday;
    

こんな感じでした。

ExtJS覚書 #1 Ext.data.ModelでREST


ExtJSでは、Ext.data.Modelを拡張したデータモデルをを使って、正しくRESTでJSONデータをやり取りする事できる。
データのライフサイクルまでそれなりに管理してくれていて便利ではあるが、何をしてくれるかを正しく理解する必要がある。
ここでは、モデルとフォーム(Ext.form.Panel)を使って一意のデータをCRUDする方法を説明する。

データモデル(Ext.data.Model)とCRUD

id, 姓, 名, 性別 という要素を持つEmployeeというデータをCRUDするRESTのクライアントの定義は以下のようになる。
idPropertyはこのデータモデルのIDとなるプロパティを指定する。
proxyのtypeに"rest"を指定することにより、Ext.data.proxy.Restが使われる。正しくRESTの型にはまる。

Ext.define('Employee', {
    extend : 'Ext.data.Model',
    idProperty : 'id',
    fields : [ 
        { name : 'id', type : 'string' },
        { name : 'firstName', type : 'string' },
        { name : 'lastName', type : 'string' },
        { name : 'gender', type : 'string' }
    ],
    proxy : {
        url : "/コンテキストルート/employeeService",
        type : "rest"
    }
});

データをロードする(loadメソッド)

このデータモデルを使ってデータをロードするコードは以下の通り。
var emp = Ext.ModelManager.getModel('Employee');
emp.load('123', {
        waitMsg : 'Loading...',
        success : function(record, operation){ },
        failure : function(record, operation){ }
        });

これで、 http://ホスト/コンテキストルート/employeeService/123 にGETリクエストが送られる。
success, failure関数の引数recordには、得られたデータ(Employeeのインスタンス)が入る。

データモデルをformにセットする

form(Ext.form.Basic)のloadRecord()を使って、データモデルをformにセットする事が出来る。
この時データモデルの内容で、formの要素は書きかえられる。
データモデルのfieldと、formの要素の名前(と型)が一致していればOK。
var emp = Ext.ModelManager.getModel('Employee');
emp.load('123', {
        waitMsg : 'Loading...',
        success : function(record, operation){
            empForm.getForm().loadRecord(record);
        },
        failure : function(record, operation){ }
        });

データを新規作成する(saveメソッド)

Ext.ModelManager.create()を使ってデータモデルを新規作成する。
save()メソッドでJSONデータをPOSTする。
var emp = Ext.ModelManager.create('Employee',{id : '123', firstName : 'Fidel', lastName : 'Castro', genger: 'male'});
emp.save({
        waitMsg : 'Saving...',
        success : function(record, operation){ },
        failure : function(record, operation){ }
        });

これで、 http://ホストコンテキストルート/employeeService に、JSONデータがPOSTされる。
success,failure時の関数の引数 record はにデータモデルのインスタンス、operationには、ext.data.Operationのインスタンスがセットされる。
ここで、setId()メソッドは使ってはならない。この件については『データを更新する』のセクションで説明する。
成功時、operationのconfig、"action"には"create"が入っている。

formのデータを使って新規作成する

フォームのデータを使って作る場合はExt.ModelManager.create()の第2引数をform.getValues()にすればよい。
formの要素と、データモデルのフィールドの名前と型は合わせておく。
var form = this.up('form').getForm();
var emp = Ext.ModelManager.create('Employee', empForm.getForm().getValues(false));
emp.save({
        waitMsg : 'Saving...',
        success : function(record, operation){
            empForm.getForm().loadRecord(record);
        },
        failure : function(record, operation){ }
        });

データを更新する(saveメソッド)

load()されたModelでsave()メソッドを使うとJSONデータがPUTされる。
この時のURLはproxyで設定したURLに"/id文字列"が付いたものになる。
上記の例で、idPropertyに設定したフィールド(デフォルトは"id")の値が"123"であった場合、
http://ホスト/コンテキストルート/employeeService/123
となる。
新規作成もsave()メソッドを使う、save()メソッドによってPOST(create)されるのかPUT(update)されるのかは、phantomというプロパティがtrueかどうかで決まる。trueならPOSTとなる。
このphantomというプロパティは、Modelのidプロパティ(デフォルトはid)に値が入っているかどうかで決まる。
model.setId(値);を実行するとphantomがfalseにセットされるようになっている。
どうもExt.data.Modelは、

  • idは一つ
  • idは新規作成時に付与される

というポリシーで作られている。
複合キーであったり、id(コード等)をユーザが決める場合はこの事を意識してプログラミングする必要がある。


formからデータモデルのインスタンスを取り出してPUTする

form(Ext.form.Basic)のgetRecord()を使って、formからModelのインスタンスを得る事が出来る。form.loadRecord()によってセットされているModelが得られる。
フォームにModelがロードされていない状態(loadRecord()されていないform)では、getRecord()はundefinedを返す。
ただし、ここで得られたrecordは、フォーム上での編集が反映されていない(編集前のデータが残っているということ)。
form(Ext.form.Basic)のupdateRecord()で、現在のフォームのデータを反映する事が出来る。

var emp = form.getRecord();    /* フォームよりデータモデルのインスタンスを得る */
form.updateRecord(emp);        /* 現在のフォーム上のデータでモデルの内容を更新する */
emp.save({
        waitMsg : 'Saving...',
        success : function(record, operation){ },
        failure : function(record, operation){ }
        });

成功時、operationのconfig、"action"には"update"が入っている。

データを削除する(destroy()メソッド) 

削除するコードは以下の通り。
var emp = Ext.ModelManager.getModel('Employee');
emp.set('123', 'Fidel', 'Castro', 'Male');
emp.destroy({
        waitMsg : 'Deleting...',
        success : function(operation){ },
        failure : function(operation){ }
        });

これで、 http://ホスト/コンテキストルート/employeeService/123 にDELETEリクエストが送られる。
saveと同様に、JSONデータが送られる。
123の部分には、データモデル定義のidPropertyに指定したフィールドの値がセットされる。