昨天來不及做Integration Test,今天才發現初始化的時候資料並未寫進資料庫,造成run integration test時出現NullPointerException,debug一段時間後就放棄了,直接到作者的forum po文詢問,同時自己用scaffolding出來的網頁新增幾筆資料,所幸Integration Test通過了,說明語法沒錯。
今天要繼續介紹Criteria語法以及簡單製作搜尋表單,Criteria特別適用於搜尋表單,搭配closure以及if可以輕鬆地將相對應的物件query出來,而where方法就比較麻煩,因where需要指定變數,而當搜尋欄變多時,我們無法預先知道使用者會輸入那些欄位,用where方法就必須多寫判斷式以免漏掉。
Criteria跟where差不多,差別在於條件是上where用的是operator如"==","in x..y"等,而Criteria則是用方法如eq()是代表equal, between()代表介於...之間,語法如下
(Domain Class Name).createCriteria().方法(){
and(or, not){條件式}}
或是
(Domain Class Name).withCriteria{and(or, not){條件式}}
首先先給大家看今天會用到的資料:
1.User Data
User以及Profile一對一欄位的文字又臭又長,之後會跟大家分享如何修改
再來需要新增搜尋表單,這裡會用到一些grails的taglib,例如g:form、g:submitButton等,不過跟原本的html差異沒有很大,可能有些而外功能目前用不到,就先照作者說的做囉
新增->Groovy Server Page,請新增4個GSP於view/user底下,分別命名search, advsearch, result, advresult
search表單,最簡易的表單,僅查詢userId
<%@ page contentType="text/html;charset=UTF-8" %>
</tr>
<tr>
<td><g:textField name="query"/></td>
</tr>
<tr>
<td><g:submitButton name="search" value="Search"/></td>
</tr>
</Table>
</g:form>
</formset>
advsearch,針對profile進行搜尋,故欄位比較多,用表格排版,另外多一個radio Button可以讓使用者選擇搜尋條件
<%@ page contentType="text/html;charset=UTF-8"%>
<div class="body">
<formset> <legend>Advance Search on Friends' Profile</legend>
<g:form action="advResults">
<Table>
<tr>
<td>Name</td>
<td><g:textField name="fullName" /></td>
</tr>
<tr>
<td>Education</td>
<td><g:textField name="education" /></td>
</tr>
<tr>
<td>Email</td>
<td><g:textField name="email" /></td>
</tr>
<tr>
<td>City</td>
<td><g:textField name="city" /></td>
</tr>
<td>Query Type:</td>
<td><g:radioGroup name="queryType" labels="['And','Or','Not']" values="['and','or','not']" value="and" >
${it.radio} ${it.label}
</g:radioGroup>
<g:submitButton name="search" value="Search"/>
</g:form>
3.再來則是要回到UserController這個class來寫一些code讓完成搜尋流程
import org.apache.jasper.compiler.Node.ParamsAction;
class UserController {
static scaffold = true
def search(){
//若未宣告則無法存取該網址
}
def advsearch(){
//若未宣告則無法存取該網址
}
def advResults(){ // 定義搜尋條件
def profileProps= Profile.metaClass.properties*.name
//從metaclass取得所有屬性並用profileProps儲存
def profiles = Profile.withCriteria {
"${params.queryType}"{
//從表單參數中queryType取得條件and, or, not
params.each{field, value->
if(profileProps.contains(field) && value){
//只要profile有該屬性且該屬性不是null則存入profiles
ilike field, value
}
}
}
}
return [profiles:profiles]
}
def results(String query){ //定義搜尋條件
//def users =User.where {userId =~ "%${query}%"} .list()
def cusers = User.createCriteria().list{
ilike "userId", "%${query}%"
/*where方法是operator組合條件
* 而criteria則是用各種方法組合條件
* 如ge()是">", ilike是"~=", between()是"in x...y"
* eq()是"=="
*/
}
return [cusers:cusers,
term: params.userId,
totalUsers: User.count()]
}
}
Note
1.任何view或是GSP網頁都需宣告後才能在網址列打入名稱存取,否則會找不到網頁
2.有關於搜尋參數如何處理必須對應到之前search以及advsearch form action的名稱
3.params即搜尋表單中所有參數的陣列
4.result,從UserController回傳的變數可以直接用${變數名稱}存取
<%@ page contentType="text/html;charset=UTF-8" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main"/>
<title>Search Result</title>
<h1>Results</h1>
<p>
Searched ${totalUsers } records
for items matching <em>${term}</em>.
Found <strong>${cusers.size() }</strong> hits.
</p>
<Table>
<g:each var="user" in= "${cusers }">
<tr>
<ul>
<td><li>${user.userId}, ${user.password}</li></td>
</ul>
</tr>
</g:each>
</Table>
<g:link action='search'>Search Again</g:link>
5.advresult
<%@ page contentType="text/html;charset=UTF-8" %>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main"/>
<title>AdvSearch Result</title>
<h1>Advanced Search Results</h1>
<p>
Found <strong>${profiles.size() }</strong> hits.
</p>
<Table>
<g:each var="profile" in= "${profiles}">
<tr>
<ul>
<td>
<li>FullName:${profile.fullName},
Education:${profile.education},
Email:${profile.email},
City:${profile.city}</li></td>
</ul>
</tr>
</g:each>
</Table>
<g:link action='advsearch'>Advance Search Again</g:link>
i.搜尋條件1
ii.搜尋條件2