iT邦幫忙

4

extjs 4 系列 5/5:企業級開發框架:extjs 與 grails 的完美組合

smlsun 2013-05-10 07:38:3911663 瀏覽

本篇的重點在於說明 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 使用,步驟如下:

  1. 利用 sencha cmd 產生 extjs 專案

sencha -sdk {extjs_home} generate app {projectName} {projectLocation}

  1. 修改 sencha 設定檔:修改 {projectLocation}/.sencha/app/sencha.cfg,加入下面兩行 :

app.dir={projectLocation}
app.classpath=${app.dir}/app.js,${app.dir}/app

  1. 進到 {projectLocation} 執行 production 編譯

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 的教育訓練,歡迎有興趣的讀者可以互相切磋。

系列文章到此告一段落,期待在次與大家分享!


2 則留言

0
總裁
iT邦好手 1 級 ‧ 2013-05-10 07:51:35

拍手拍手灑花灑花恭喜完工!!!

smlsun iT邦新手 4 級‧ 2013-05-10 21:25:14 檢舉

感謝!多多指教喔~

0
Kyle
iT邦新手 3 級 ‧ 2013-05-10 11:06:37

恭喜大大賀喜大大~驚人的五連發 :XD

smlsun iT邦新手 4 級‧ 2013-05-10 21:25:47 檢舉

哈哈~還太嫩,要在努力!

我要留言

立即登入留言