本篇的重點在於說明 extjs 作為 web 應用程式前端的 framework,如何與目前常用的 full stack framework 進行整合,在此將以 grails 為例,當然不只可以跟 grails 整合,其他像 RoR 或者 .net,甚至是 node.js 都可以作為 extjs 的後端服務提供者。
透過 grails 這樣的整合範例,希望可以讓讀者體會不只能夠快速開發,一旦應用程式大到一定程度,也可以很方便的維護,並且在開發流程中的循環都可以順暢的不停轉動。
第一天:extjs 4 的新特性與基本概念 。
第二天:使用 extjs 必須了解的 js 技巧與知識 。
第三天:extjs mvc 架構:繼承與模組重覆使用 。
第四天:利用 sencha architect 快速開發 extjs。
第五天:企業級開發框架:extjs 與 grails 的完美組合(本篇)。
<span style="color: green;"><span style="font-size: 30px;">前後端分工</span></span>
開發大型軟體,或是時程上較趕的時候最怕等來等去,在開發應用程式時,最需要確認的是資料庫的設計,一旦定義好之後,如何快速完成 model 並且將測試資料建立完成,以便進行測試,透過 grails 與 extjs 剛好可以完美的解決此問題,前幾篇介紹到的關於 extjs model 類別的使用,其概念與 grails 剛好一拍即合,同樣以之前 extjs mvc 為例 裡面用到的 Item,batch,以及 itemImage,在 grails 中宣告如下:
package finder
class Item {
String name
String title=""
String description=""
static hasMany=[itemImages:ItemImage]
}
package finder
class Batch {
String name
static belongsTo =[item:Item]
}
package finder
class ItemImage {
Item item
String name
}
grails 可以把它看做 java 中的 RoR,因此也有「約定優於配置」的特性,以往在傳統 java 對於 O/R mapping 這樣的技術,往往需要大量的 xml 定義,在 grails 只要將寫好的 model 放在 grails 下的 model 資料夾,而三個資料表定義就像上面的程式碼一樣,輕鬆簡單!不需要在對資料庫進行 table create,一旦 grails 啟動就會檢查資料庫是否有對應的資料表,判斷若是 develop mode 將會使用虛擬資料庫,在記憶體中就會建立好三個 table,不需要有實體就可以開始對你的應用程式開始進行測試,一旦開發完成,只要進行設定轉換為實體資料庫即可,接著在 grails 中有個類別 BootStrap 在這裡可以定義你要測試的初始資料,以便進行相關應用開發,如下:
import finder.*
class BootStrap {
def init = { servletContext ->
environments {
development {
def item1 = new Item(name:"item1").save(failOnError: true, flush: true)
def batch1 = new Batch(name:"batch1",item:item1).save(failOnError: true, flush: true)
def itemImage1 = new ItemImage(name:"itemImage1.jpg",item:item1).save(failOnError: true, flush: true)
}
}
}
def destroy = {
}
}
一旦伺服器啟動就會執行在 BootStrap 中的程式碼,如果我們在此區塊撰寫新增資料的程式,每次啟動 grails 都會有新的資料可以進行測試,反覆測試的過程中將免去每次都要建立測試資料的麻煩,並且有預設的設定值也可以在此定義,資料準備好了,前後端就可以分開進行,接著來看如何快速定義好 extjs 與 grails 的溝通橋樑。
<span style="color: green;"><span style="font-size: 30px;">以 RESTful 進行前後端溝通</span></span>
extjs 4 有個新的 proxy type:rest,一但定義為 rest proxy,在資料操作上將會根據你對前端資料的更新動作給予不同的 http method,如下:
* 新增:POST
* 修改:UPDATE
* 刪除:DELETE
* 查詢:GET
我們會用到另一個敏捷開發特性:Don’t Repeat Yourself(DRY),在 Grails 有另一個設定檔 URLMappings 可以讓我們設定根據前端 request 的 http method 導入至特定後端 controller method,該檔案設定如下:
class UrlMappings {
static mappings = {
"/$controller/$action?/$id?"{
constraints {
}
}
"/rest/$controller/$id"{
action = [GET: "show", PUT:"update", DELETE:"delete"]
constraints {
}
}
"/rest/$controller"{
action = [GET:"listAll", POST: "create"]
constraints {
}
}
"/"(view:"/home/index")
"500"(view:'/error')
}
}
可以看到在 URLMappings 的設定中:
"/rest/$controller/$id"{
action = [GET: "show", PUT:"update", DELETE:"delete"]
constraints {
}
}
"/rest/$controller"{
action = [GET:"listAll", POST: "create"]
constraints {
}
}
代表如果有傳入 id 則是上述的第一種 mapping 方式,根據 http method 的不同對應到不同的 controller 的 method;若沒有 id 則是第二種,實際代表的網址可能為 http://localhost/rest/item/1
或者 http://localhost/rest/batch/
,就會根據 UrlMappings 的定義觸動在 controller 中的 method,範例如下:
package finder
import grails.converters.JSON
class ItemController {
def listAll = {
def items=Item.list()
render (contentType: 'text/json') {
[
items: items,
total: items.size()
]
}
}
def show = { Long id ->
def item=Item.findById(id)
render (contentType: 'text/json') {
[
item: item
]
}
}
def create = {
...
}
def update = {
...
}
def delete={
...
}
}
剛剛提到對應的 controller method 就如同上面程式碼中的 listAll,show 等等,到這邊,後端的 server 算是已經準備好,可以開始進行測試,是否發現跟一般 java 比,簡潔很多,寫起來還有點像 javascript?實際上 Grails 骨子裡還是 java,執行時會編譯為 class,因為搭配了 java 中的動態語言 groovy 才有這樣的效果,且並沒有捨棄 java 多年累積廣大的第三方套件,當你需要時皆可以引入,不需重新造輪。
<span style="color: green;"><span style="font-size: 30px;">extjs:store.sync() - 簡化更新</span></span>
後端 server 快速準備好後,在 extjs 更加簡化呼叫更新資料請求的程序,在 store 的類別提供一個 method 為 sync(),作用在於一旦 store 載入後,只要對 store 執行 insert,remove,insert 確定更新完成後,一旦執行就會對後端 server 發出 http request,所以,你不用勞你費心,extjs 已幫你完成相關程序,範例 controller 如下:
Ext.define('Frontend.controller.common.Standard', {
extend: 'Ext.app.Controller',
doRead: function() {
this.store.load();
},
doCreate: function() {
this.store.insert(0, this.model);
},
doDelete:function(){
var selection = this.grid.getSelectionModel().getSelection()[0];
if (selection) {
this.store.remove(selection);
}
},
doUpdate: function() {
//更新對 store 的異動
this.store.sync({
success : function(){
console.log("success");
Ext.Msg.alert('Status', '更新成功');
},
failure : function(response, options){
console.log("failure");
Ext.Msg.alert('Status', '更新失敗');
}
});
}
});
<span style="color: green;"><span style="font-size: 30px;">快速前端元件建立</span></span>
即使用像 grails 這樣的 full stack framework 對於前端介面還是需要自己重頭刻起,若是搭配 sencha architect 將可以補齊這方面的不足:快速建立前端介面,並且為了敏捷快速的開發,一旦介面拉好,就可以儘快確認需求與操作介面,所完成的介面就可以開始著手開發,介面的變動也可以在 architect 中完成,還記得之前有介紹過在 extjs 中的每個小元件都可以作為類別存在,並且 controller 若以每個元件為目標設計,透過混和(mixins)的特性組合 controller 就可以快速調整介面的呈現與互動。
<span style="color: green;"><span style="font-size: 30px;">extjs develop mode & test</span></span>
一個好的框架,必須還要能夠方便測試,在 extjs 中可以很方便的指定某個類別作為初始的 view,可以參考上一篇Sencha Architect 快速開發 extjs 中「方便進行測試與開發」的介紹,即使你沒有用 Architect,也可以自行定義,別忘了利用這樣的特性對開發中的介面進行測試。
<span style="color: green;"><span style="font-size: 30px;">extjs production mode</span></span>
應用程式開發到一個階段,就會從 develop 進階到所謂的 production mode,其目的就是要盡量加速資源的載入,在前端的世界就是要將所有的 js 檔最小化,並且合為一個 js 檔,雖然 extjs 有動態載入,實際在 production 模式這樣是很耗效能的,如果我們要自行利用 minify 工具進行壓縮,在 extjs 中各類別的相依性就無法顧慮到,並且可能因為組成檔案順序不正確造成衝突,所幸,extjs 也注意到這樣的問題,提供 Sencha cmd 來處理 minify js 的程序,並且可以搭配 Architect 使用,步驟如下:
sencha -sdk {extjs_home} generate app {projectName} {projectLocation}
app.dir={projectLocation}
app.classpath=${app.dir}/app.js,${app.dir}/app
sencha app build production
如此一來就會將執行完的結果產出在 {projectLocation}/build 底下,就是這們簡單!
<span style="color: green;"><span style="font-size: 30px;">resource 控管</span></span>
extjs 所完成的介面在 grails 中將作為 resource 存在,且對 grails 而言屬於靜態檔案,因此可以進行快取來加速資源載入,而在 grails 有一設定檔 ApplicationResources 專門在定義要載入的 resource,在設定時必須考慮 develop 與 production 的不同,設定方式如下:
import org.codehaus.groovy.grails.web.context.ServletContextHolder as SCH
modules = {
// develop mode 使用
extjs4_dev {
defaultBundle 'finder_dev'
resource url: 'extjs4_dev/resources/ext-theme-neptune/ext-theme-neptune-all.css'
resource url: 'ext/ext-all.js'
resource url: 'ext/ext-theme-neptune.js'
resource url: 'app.js'
getFilesForPath('app').each {
resource url: it
}
}
// production mode 使用
extjs4 {
defaultBundle 'finder'
resource url: 'extjs4/resources/finder_extjs-all.css'
resource url: 'extjs4/all-classes.js'
}
}
// 載入 path 參數底下所有的檔案作為 resource
def getFilesForPath(path) {
def webFileCachePaths = []
def servletContext = SCH.getServletContext()
if(!servletContext) return webFileCachePaths
def realPath = servletContext.getRealPath('/')
def appDir = new File("$realPath/$path")
appDir.eachFileRecurse {File file ->
if (file.isDirectory() || file.isHidden()) return
webFileCachePaths << file.path.replace(realPath, '')
}
webFileCachePaths
}
經由這樣的設定,grails 會自動將 block 中所定義的 js 檔自動合為單一 js 檔,接著我們只要在 grails 中特有的 gsp 加入下列判斷:
<g:if env='development'>
<r:require modules="extjs4_dev"/>
</g:if>
<g:else>
<r:require modules="extjs4"/>
</g:else>
就會根據不同的開發模式載入不同的 resource 組合。
<span style="color: green;"><span style="font-size: 30px;">打完收工,下次在相會!</span></span>
這是個想法,目前我們也正在投入這樣的應用,預期可以帶來不一樣的開發方式,軟體開發方式不停的在進步,也許還有很多團隊還在使用老舊的方法,這樣的組合,除了可以敏捷快速的開發,利用 extjs 所提供的方便性,相信可以帶來效率的提升,特別是前端的物件建立與操作,表單式的應用程式非常適合,筆者也曾在企業進行 extjs 的教育訓練,歡迎有興趣的讀者可以互相切磋。
系列文章到此告一段落,期待在次與大家分享!