Pages

搜尋此網誌

2013年12月25日 星期三

extjs 更無縫的與 grails 進行整合

extjs 更無縫的與 grails 進行整合

筆者之前在寫過一篇關於 extjs 與 grails 的完美組合,大概說明了 extjs 與 grails 整合所帶來的優點,以及兩者 mvc 的結構可以更快速的開發出容易維護的應用程式,更進一步的我們希望兩者的連結對於資料的 mapping 與更新能夠更加直接。

標題所說的更無縫的整合,在這所要說明的正是關於資料的傳遞,在開發 web 應用程式時,很多時候我們需要處理前端資料結構與後端物件的 mapping,以便進行資料的增修改查,假設我們需要建立一個 grid 如下圖:

image

grials 的 domain 與 extjs 的 model 定義

畫面以及元件的組立在這就不多做說明,會與後端有關係的就是 domain 與 model的定義,兩者各自代表系統在運作時的資料結構,雖然命名不同,性質是一樣的,假設我們有兩個定義在 grails 的 domain 如下:

class Item {

    String name
    String title=""

    static constraints = {
    }
}

class Batch {

    static belongsTo=[Item:item]
    String name


    static constraints = {
    }
}

很簡單的兩層結構,在 extjs 也要有對應的 model 如下:

Ext.define('MyApp.model.Item', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'id'
        },
        {
            name: 'title'
        },
        {
            name: 'name'
        }
    ],

    proxy: {
        type: 'rest',
        url: '/rest/item',
        reader: {
            type: 'json',
            root: 'itemInstanceList',
            totalProperty: 'itemInstanceTotal'
        }
    }
});


Ext.define('MyApp.model.Batch', {
    extend: 'Ext.data.Model',

    fields: [
        {
            name: 'id'
        },
        {
            name: 'name'
        },
        {
            mapping: 'item.id',
            name: 'item.id'
        }
    ],

    proxy: {
        type: 'rest',
        url: '/rest/batch',
        reader: {
            type: 'json',
            root: 'batchInstanceList',
            totalProperty: 'BatchInstanceTotal'
        }
    }
});

同樣定義了相同的資料結構只不過在 extjs 前端多了 id 是因為在 grails 中 id 欄位將會自動產生,不需要經過定義。

grials 與 extjs 的資料交換與 mapping

接著要說明兩者之間的資料如何進行交換傳遞,在上述 extjs 的 model 中,proxy 定義了與後端溝通的方式,在這邊我們使用 rest 的方式,reader 定義了回傳回來的資料該如何進行解析,範例的回傳結構如下:

{
    "batchInstanceList": [
        {
            "class": "extjs.grails.sample.Batch",
            "id": 1,
            "item": {
                "class": "Item",
                "id": 1
            },
            "name": "batch1"
        },
        {
            "class": "extjs.grails.sample.Batch",
            "id": 2,
            "item": {
                "class": "Item",
                "id": 1
            },
            "name": "batch2"
        }
    ],
    "batchInstanceTotal": 3
}

其中 root 與 totalProperty 所指定的就是在回傳 json 中對應的屬性名稱,更進階的,我們可以多層如同:

{

    "itemInstanceList": [
        {
            "class": "extjs.grails.sample.Batch",
            "id": 1,
            "item": {
                "class": "Item",
                "id": 1
            },
            "name": "batch1"
            "route": {
                [
                    {
                        id:1
                        name:'route1'
                    },
                    {
                        id:2
                        name:'route2'
                    }

                ]
            },
        },
        {
            "class": "extjs.grails.sample.Batch",
            "id": 2,
            "item": {
                "class": "Item",
                "id": 1
            },
            "name": "batch2"
        }
    ],
    "batchInstanceTotal": 3

}

這樣的結構下我們可以這樣定義 root:

root: 'batchInstanceList.route',

很多時候 model 會有 hasMany 的關係,json 解析時清楚看到資料的結構,實際使用時也可以根據不同情形取出不同層級的資料內容進行 grid 的呈現。

另一種情形是為了節省 request 的量,會希望一次交易就將多組資料一次從後端取得,可以指定多層的索引方式就格外重要。

說明完了資料結構的定義,我們在進一步來看單筆資料的回傳,假設我們利用 ajax 將資料送出並且回傳格式為 json 內容如下

{
    id: 1
    item_id: 1
    name: "batch2"
}

後端 grails 接收到這樣的資料時,前端所傳入的參數值都存入 params 這個變數以便進行存取取得任何資料,就跟使用 json object 一樣,我們可以這樣進行更新:

// update
def batchInstance=Batch.get(params.id)
batchInstance.properties = params
batchInstance.item=Item.findById(params.item_id)
batchInstance.save()

// create
def batchInstance=new Batch(params)
batchInstance.item=Item.findById(params.item_id)
batchInstance.save()

在使用 grails 時,雖然 update 與 create 的使用方式不同,但我們都可以將整個 json 傳入 domain 讓 grails 自動幫我們進行資料的 mapping,透過 json 裡的每個參數名,而在有多層架構下,初學使用可能會向上述範例程式一樣,需要在查詢一次下層 domain,其實 grails 對於多層的物件也可以自動 mapping 並且查出,假設我們的回資料結構改變為下:

{
    id: 1
    item.id: 1 // item_id >> item.id
    name: 'batch'
}

如此一來在 grails 中我們可以這樣存取 item.id:params.item.id,也就是說傳入 grails 的參數如果是有多層結構,我們可以在前端 field 之 id 定義成對應的多層結構的如同上述範例,即使是使用 form 的格式也是一樣對應 input field 的 name。

調整完後我們的 update 與 create 可以精簡如下:

// update
def batchInstance=Batch.get(params.id)
batchInstance.properties = params
batchInstance.save()

// create
def batchInstance=new Batch(params)
batchInstance.save()

這樣的特性有什麼好處?在不需要特殊的欄位處理下,對於 domain 欄位的調整或新增我們不需要變動到 update 與 create 的處理,所有中間的資料 mapping 都交由 grails 幫我們處理完成,相信還有更重要的事值得我們去花時間。

關於 grails 的 data binding 方式,讀者可以參考 grails 的官方 7.1.6 Data Binding,裡面有其他更進階方變得 binding 規則,其中在筆者的專案中有遇到一個問題,在要將關連 domain 設為空的時候傳入 null 值一直無法正確運作,在官方說明 binding 的章節中有一段說明:

An association property can be set to null by passing the literal String “null”.

像是這種情形,就應該要把要設為 null 的物件其值改為 “null”,如此一來我們就可以把關連 domain 設為空,移除該關連。

sencha architect 使用注意事項

最後還要在說明的是在使用 architect 定義前端 model 的 name 時不允許輸入 “.” 會跳出下圖訊息:

image

你只要先用 ‘_’ 將他建立完成,之後再進入 field 將 name 中的 _ 改為 ‘.’ 即可,算是兩邊的限制不一樣,基本上 field 中的 name 有 ‘.’ 是不影響運作的。

張貼留言