摘自: IBM developerWorks China
選擇 Grails 和 Dojo 的原因
隨着 web 的廣泛應用,web application 的開發項目越來越多,而大部分的 web 開發歸根結底都是對數據庫的增刪改查。對於一張數據庫表的增刪改查,採用基於 MVC(模型 - 視圖 - 控制器)設計模式的開發往往需要 Domain class、 Controller、4 個增刪改查的頁面、form 輸入數據的校驗等。這樣就免不了大量類似功能的複製粘貼修改。 如果你想從重複勞動中解放出來,Grails 是一個很好的選擇。 使用 Grails 只需要寫一個 domain class 就可以自動生成
CRUD 4 個頁面和對應的 controller 方法來實現對數據庫的增刪改查,並實現服務器端的數據校驗,從而大大地提高了開發效率。
類似的 web 快速開發框架還有 Rails、Django 和 TurboGears 等,與它們相比 Grails 有如下優勢:
- Grails 是一套用於快速 Web 應用開發的開源框架,它是由 15% 的 Groovy 代碼和 85% 的 Java 代碼寫成,並建立在千錘百煉的經典框架(Spring,Hibernate,Sitemesh)之上,從而爲大家帶來一套能實現超高生產力的一站式框架。
- Grails 學習快速,使用容易,如果你熟悉 Hibernate、Spring、SiteMesh 和 JSP,那麼你一個小時之內就能學會 Grails。
- Grails 運行於 JVM 之上,生成的 war 包可以部署在各種成熟的服務器 Tomcat, JBoss,Weblogic,Webspere 等。
- 性能與 Rails2(100% Ruby) 相比要快很多。
- Grails 被 SpringSource 收購,有強大的技術支持。
採用 Grails 的默認模板生成的 CRUD 頁面有些簡陋,實際項目中往往需要功能更加豐富,外觀更加漂亮,使用更加方便的控件。例如頁面佈局控件,Tab 控件,樹,菜單,日曆,編輯器,進度條等等。現在有很多 JS 框架致力於 web UI, 例如 Dojo, Extjs, Jquery, YUI 等。我們之所以選擇 Dojo, 因爲 Dojo 具有以下的優勢:
- 組件豐富,有強大的 UI(Dijit)。這樣一來就可以減輕我們的大量沉重的工作,而且目前的開發非常活躍。
- UI 外的功能也很強大,Full Stack 的框架,擴展了 DHTML 的能力,例如:
- 支持與瀏覽器 Back/Forward 按鈕的集成。
- Dojo Offline,一個跨平臺的離線存儲 API。
- Chart 組件,可以方便地在瀏覽器端生成圖表。
- 基於 SVG/VML 的矢量圖形庫。
- Google Maps、Yahoo! Maps 組件,方便開發 Mashup 應用。
- Dojox Socket, 基於 WebSocket 或者 XHR 長連接的“服務器推”技術。
- Dojo 是一個很好的基礎架構,具有面向對象的設計,統一的命名空間,包管理機制(The Package System and Custom Builds)可擴展性。Dojo 其實是一個組件模型,類似於 Spring,用來支持大規模的組件化開發。組件模型的作用就是增強代碼的重用,這對於提高開發效率是至關重要的。
- Dojo 的背後有強大的支持 IBM、Oracle 等,這是非常重要的優勢。Dojo 現在已經是衆多開源框架的選擇,包括:WebWork、Tapestry、Eclipse ATF、MyFaces。Dojo 的開發團隊由 Alex Russell 領軍,人數衆多,力量非常強大。
- 開源,使用 BSD 軟件許可。
回頁首
快速搭建 CRUD 的框架 (MVC)
開發環境配置
首先需要安裝 JDK,並設置好 JAVA_HOME。本文使用的是 JDK 1.6。
- Grails 官方網站下載並解壓 grails.zip。本文使用的是 Grails1.3.7。
- 創建一個 GRAILS_HOME 環境變量,將 $GRAILS_HOME/bin 添加到 PATH 中。
- 習慣用 IDE 的可以選擇 Intellij idea,Netbeans 和 Spring STS。本文以免費的 Netbeans7.0.1 爲例,配置很簡單,只需要在 Tools->Options->Miscellaneous->Groovy 下設置 Grails Home,就可以進行 Grails 項目的開發了。(見圖 1)
圖 1. Netbeans 中 Grails 的配置
命令行創建 web application
- 首先,在一個空白目錄下,輸入 grails create-app DojoGrails。稍後,可以看到一個名爲 DojoGrails 的目錄 , 會建立一個標準的目錄結構(如圖 2)。很明顯這是層次清晰的 MVC 模式。
圖 2. Grails 的目錄結
- 進入 DojoGrails 目錄,並輸入 grails create-domain-class com.shuo.Employee,將得到兩個新的文件:域類 grails-app\domain\com\shuo\Employee.groovy 和一個單元測試類。這裏我們主要關注域類。一開始,域類裏面什麼都沒有,我們爲它添加一些字段和約束,見清單 1:
清單 1. 域類 Employee
class Employee {
String employeeNumber
String name
Integer age
Date onboardDate
static constraints = {
employeeNumber blank: false, unique: true
name blank: false
}
String toString(){
return "${name}(${employeeNumber})"
}
}
|
類似地我們創建一個與 Employee 關聯的域類 Product。Product 的負責人 owner 是 Employee。見清單 2:
清單 2. 域類 Product
class Product {
String name
String description
Float price
Employee owner
static constraints = {
}
}
|
這兩個域類覆蓋了所有常見的數據庫數據類型(Date,String,Integer,Float)。
- 在命令行輸入 grails create-controller com.shuo.Employee 生成域類對應的 Controller, 使用 grails 強大的腳手架 scaffold,見清單 3。運行時在內存中將動態地生成所有 CRUD 的 method 以及 CRUD 的頁面。同樣地生成 ProductController。
清單 3. 控制器代碼
class EmployeeController {
def scaffold = true
}
class ProductController {
def scaffold = true
}
|
- 到這裏對兩張表的 CRUD 功能全部完成,是時候看看效果了,在命令行輸入 grails run-app。通過瀏覽器 http://localhost:8080/DojoGrails, 可以看到如圖 3 所示的 Employee 的 List 頁面。
圖 3. Scaffold 默認的 List 頁面
可以看出這個 list 頁面已經具備分頁和排序功能了。
再來看看 Employee 的 Create 頁面,如圖 4 所示:
圖 4. Scaffold 默認的 create 頁面
從圖中可以看出添加後,服務器端會返回數據驗證的結果,比如員工號必須唯一,名字不能爲空,年齡必須是數字等。
再來看看 Product 的 Edit 頁面 , 如圖 5:
圖 5. Product 的 Edit 頁面
這裏我們主要關注 owner 字段 , scaffold 自動生成一個列出所有員工的下拉框,讓用戶從中選擇出產品的負責人。
另外值得一提的是,這個 web application 已經擁有了國際化多語言的支持,只需要對 grails-app/i18n/messages_zh_CN.properties 文件進行修改,就可以漢化整個 web 界面了。
Grails 的 Scaffold 腳手架是不是很強大呢,一句 def scaffold = true 就實現瞭如此豐富的功能。
-
在命令行輸入 grails generate-all com.shuo.Employee,grails 會根據默認模板在 EmployeeController 中生成 CRUD 的對應的 method 代碼,並在 grails-app/views/employee 下生成 4 個 CRUD 的 gsp 頁面。這些就是 scaffold 背後的代碼。後面我們將對這些代碼進行修改,引入 Dojo,來增強 CRUD 的用戶體驗。
回頁首
使用 Dojo 增強 CRUD 用戶體驗
引入 Dojo
到 dojotoolkit.org 網站下載最新版本的 Dojo Toolkit Release。 本文用的是 dojo-release-1.6.1。
解壓後將其拷入 web-app/js 中,目錄結構如圖 6:
圖 6. Dojo 的目錄結構
在 grails-app/views/layouts/main.gsp 中添加以下代碼,見清單 4:
清單 4. 導入 Dojo
<link rel="stylesheet"
href="${resource(dir:'js/dojo-release-1.6.1/dijit/themes/claro',file:'claro.css')}"
/>
<script src="http://www.ibm.com/developerworks/cn/web/1204_zhanzy_dojograils/${resource(dir:'js/dojo-release-1.6.1/dojo',file:'dojo.js')}"
data-dojo-config="isDebug: true,parseOnLoad: true,locale:'zh'"></script>
…
<body class="claro">
|
使用 EnhancedGrid 增強 List 表格
Grails scaffold 默認的 List 表格(如圖 3)功能較爲簡單,例如它不能像 excel 那樣的跨行列合併單元格、凍結表頭、單擊單元格轉成編輯狀態等。而 Dojo 爲我們提供了這樣的控件:Dojox 的 DataGrid 像一個基於 Web 的 Excel 組件,足可以應付非常複雜的數據展示及數據操作。下面列出了 DataGrid 的一些特性:
- 可以任意的增加和刪除單元格、行、或者列;
- 對行進行統計摘要,Grid 可以生成類似於 OLAP 分析的報表;
- Grid 超越了二維表格的功能,它可以跨行或跨列合併單元格以滿足不同的數據填充的需求;
- 行列凍結功能,使得瀏覽數據更加靈活方便;
- Grid 事件採用了鉤子機制,我們可以通過 onStyle 鉤子完成對樣式的更改;
- 單元格具備富操作,所有的 dijit 部件都可以在單元格中使用,並且單元格可以通過單擊轉換爲編輯狀態;
- 可以爲不同的單元格設置不同的上下文菜單;
- Grid 嵌套,也就是說 Grid 可以在單元格中嵌套其他的 Grid,從而組成更爲複雜的應用
- 支持 Dnd 拖放和鍵盤 navigation,提高了 Accessibility。
- 除此之外,Grid 還有具有其他很多特性,例如,非常實用的偶數行上色、靈活的選取功能、自動調整列寬、數據的展開/合閉等。
其中 DataGrid 有一個非常重要的特性就是虛擬滾動(Virtual Scroll),DataGrid 對付大數據源的時候,在滾動事件觸發後才請求後面的數據並創建 DOM 結點,因此每次只需要顯示很少的幾行,從而加快了 Grid 的加載。 不得不承認這是一種很精巧的做法,在用戶體驗上也很自然,就好像所有數據本來就在那裏一樣。但它也有缺點,當數據源真的比較大的時候,滾動條就會非常小,再加上行高參差不齊的情況,要精確地滾動到某個位置就比較困難。這時候簡單直觀的分頁機制就足夠了。最新的 Dojo 1.6
版裏,繼承自 DataGrid 的 EnhancedGrid 引入了幾個新的插件,其中就有 Pagination(分頁瀏覽)插件。
首先在 list.gsp 中使用 EnhancedGrid, 見清單 5:
清單 5. Dojo 的 EnhancedGrid 代碼
<script type="text/javascript">
dojo.require("dojox.grid.EnhancedGrid");
dojo.require("dojox.grid.enhanced.plugins.Pagination");
dojo.require("dojox.data.QueryReadStore");
var myStore = new dojox.data.QueryReadStore({url:"listJson"});
function onRowDblClick(e){
var itemid = grid.getItem(e.rowIndex).i.id;
document.location.href="edit/"+itemid;
}
</script>
<style type="text/css">
@import "${resource(dir:'js/dojo-release-1.6.1/dojox/grid/enhanced/resources/claro',
file:'EnhancedGrid.css')}";
</style>
…
<table dojoType="dojox.grid.EnhancedGrid" jsId="grid" store="myStore"
rowsPerPage="5" clientSort="true" style="width: 100%; height: 280px;"
onRowDblClick= "onRowDblClick"
rowSelector="20px" plugins="{ pagination: {
pageSizes:['5','10','20'],
maxPageStep: 5,
descTemplate: '${message(code: 'default.paginateDescTemplate')}',
description: true,
sizeSwitch: true,
pageStepper: true ,
gotoButton: true
}}">
<thead>
<tr>
<th width="50px" field="id" >ID</th>
<th width="100px" field="employeeNumber"> 員工號 </th>
<th width="100px" field="name"> 姓名 </th>
<th width="100px" field="age"> 年齡 </th>
<th width="100px" field="onboardDate"> 入職日期 </th>
</tr>
</thead>
</table>
|
在 employeeController 中加入方法,見清單 6:
清單 6. EnhancedGrid 對應的控制器代碼
def listJson = {
if(params.start?.isInteger()){
params.put("offset",params.int('start'))
}
params.put("max",params.count?params.int('count'):5)
if(params.sort?.startsWith("-")){
params.put("sort",params.sort.substring(1))
params.put("order","desc")
}
def total = Employee.count();
def results = Employee.list(params)
def jsonData = [identifier:"id",numRows: total,items: results]
render jsonData as JSON
}
|
這樣就完成了 EnhancedGrid 和服務器,數據庫的交互。以上代碼實現了分頁、排序、雙擊表中一行進入修改界面,效果如圖 7:
圖 7. EnhancedGrid 界面
以上代碼有兩點需要注意:
- 從圖 3 可以看出默認的日期格式是 yyyy-MM-dd HH:mm:ss z,這是由 messages.properties 中的 default.date.format 定義的。而對於 JSON 中的日期數據,格式化需要在 config.groovy 中加入如清單 7 的代碼:
清單 7. JSON 的日期格式化代碼
import grails.converters.JSON;
class BootStrap {
def init = { servletContext ->
JSON.registerObjectMarshaller(Date) {
return it?.format("yyyy-MM-dd")
}
}
...
|
- 這段代碼有個小技巧。因爲 ${} 在 gsp 文件中是保留字,如果把 pagination 插件的 descTemplate 屬性值“
${2} - ${3} 共 ${1}${0} ” 直接寫在 gsp 頁面上會出錯,所以寫到 messages 文件裏。
default.paginateDescTemplate=${2} - ${3} 共 ${1}${0}
|
使用 FilteringSelect 增強下拉框
Grails scaffold 爲外鍵關聯的對象做了下拉框供用戶選擇,而這個下拉框是個簡單的 HTML select 控件(如圖 5),用戶只能從一堆下拉選項中肉眼找出要選的關聯對象。本文例子中,用戶就需要從一大堆的企業員工中肉眼找出要選的產品負責人。企業員工常常是成百上千人,要從 select 下拉框中找出一個很難,而且一次性從數據庫讀取所有員工的姓名到下拉框也很耗時。Dojo 爲我們提供了一個類似於 HTML 的 select 控件:FilteringSelect,但它可以動態輸入,並且按照輸入值列出匹配的可選項,甚至可以按需設置加載選項的數量,下拉列表的選項可以從數據庫動態獲取。這樣用戶就能通過輸入關鍵字,讓程序幫我們找出要選的對象。
Dojo 的 ComboBox 跟 FilteringSelect 非常類似,不過 FilteringSelect 不允許用戶輸入可選項之外的值,而 ComboBox 可以輸入任意值。所以這裏我們選擇 FilteringSelect。
首先對 create.gsp 和 edit.gsp 做修改,如清單 8:
清單 8. Dojo 的 FilteringSelect 代碼
<script type="text/javascript">
dojo.require("dijit.form.FilteringSelect");
dojo.require("dojox.data.QueryReadStore");
var employeeSelector= new dojox.data.QueryReadStore({
url:"${resource(dir:'employee')}/combo"});
</script>
…
<input name="owner.id" dojoType="dijit.form.FilteringSelect"
store="employeeSelector" placeHolder="請選擇員工" pageSize="5"
autocomplete="false" value="${productInstance?.owner?.id}"
queryExpr="${message(code: 'default.queryExpr')}"></input>
|
再爲 employeeController 增加方法,從數據庫獲取下拉選項,如清單 9:
清單 9. FilteringSelect 對應的控制器代碼
def combo ={
def criteria='',results
if(params.id){
results = Employee.get(params.id)
}
if(params.name){
criteria = params.name.replace('*','%')
results =
Employee.findAllByNameLikeOrEmployeeNumberLike(
criteria,criteria, [max:params.count?(params.count+1):1,offset:params.start])
}
render(contentType: "text/json") {
identifier = "id"
label = "name"
items = array{
results.each {w ->
item("id":w.id,"name":w.name)
}
}
}
}
|
以上代碼實現了按照員工姓名模糊查找選擇員工(如圖 8 左上),按照員工號模糊查找選擇員工(如圖 8 中),查找結果分頁(如圖 8 右),如果輸入框的值不在員工中,有出錯提示(如圖 8 左下)。
圖 8. FilteringSelect 界面
使用 DatePicker 增強日期選擇
Grails scaffold 提供下拉框讓用戶選擇年月日(如圖 4),從下拉框中選較爲麻煩。常常我們喜歡直接輸入日期或者是查看日曆找到想填的日期。如圖 9 的日期選擇控件就滿足了我們的需要。這個界面漂亮操作簡單的日期選擇控件就是 Dojo 的 DatePicker,我們可以通過在頁面加入以下代碼來實現,見清單 10:
圖 9. DatePicker 界面
清單 10. Dojo 的 DateTextBox 代碼
<input type="text" name="onboardDate"
dojoType="dijit.form.DateTextBox" required="true"
value="<g:formatDate date='${employeeInstance?.onboardDate}'/>" />
|
因爲默認的 scaffold 時間控件的年月日是分 3 個字段分別上傳,和我們用一個字段上傳的數據結構是不同的,所以 controller 中需要對其做特殊轉換,見清單 11:
清單 11. 使用 DateTextBox 後,save 方法添加的日期轉換代碼
def save = {
if(params.onboardDate!=null&&!params.onboardDate.isEmpty()){
try{
params.onboardDate =Date.parse("yyyy-MM-dd",params.onboardDate)
}catch(Exception e){
employeeInstance.errors.rejectValue("onboardDate",
"typeMismatch.java.util.Date",
[message(code: 'employee.label', default: 'Employee')] as Object[],
"日期格式不對")
render(view: "create", model: [employeeInstance: employeeInstance])
}
}
def employeeInstance = new Employee(params)
...
}
|
客戶端數據校驗
Grails 的腳手架已經具備了服務器端數據校驗的功能,但是每次都將數據提交到服務器再進行校驗,性能太低了。爲減輕服務器的負載,在瀏覽器端做 Javascript 校驗,就非常必要。Dojo 正好提供了一個很好的數據檢驗框架。
用戶輸入數據的同時 Dojo 就會進行數據檢驗,一旦出錯立即高亮顯示出錯信息。
例如用 dijit.form.NumberTextBox 可以進行數字的校驗,下面的代碼要求價格是大於等於 0 的數字,見清單 12:
清單 12. NumberTextBox 的數據驗證
<g:textField name="price"
value="${fieldValue(bean: productInstance, field: 'price')}"
dojoType="dijit.form.NumberTextBox" constraints="{min:0}"
required="true" invalidMessage="大於 0 的數字 ." />
|
另外,當表單提交時可以判斷表單是否有效,如果包含無效數據將不提交表單。見清單 13:
清單 13. 表單的數據驗證
function validateData(form1){
if(!form1.validate()) {
alert('請先糾正無效數據,然後再提交!');
return false;
}
return true;
}
…
<g:form method="post" dojoType="dijit.form.Form" onsubmit="return validateData(this);" >
|
以上代碼的效果類似於圖 8(左下)中的紅邊框及出錯提示。
回頁首
結束語
從前面的文章我們已經能窺見 Dojo 結合 Grails 開發 web application 的強大能力以及效率。此外 Dojo 還有很多優秀的佈局控件,對話框,圖表,菜單,多文件上傳控件等等,能夠實現非常複雜的企業級應用。即將發佈的 Grails2.0 也添加了很多很好的特性。我們有理由相信 Dojo 結合 Grails 的前途非常廣闊。
|