iT邦幫忙

DAY 26
0

邊看邊學Groovy/Grails/Gradle系列 第 23

Grails-建立一對一物件基本註冊表單及如何上傳圖片

昨天僅介紹跳脫scaffold所產生的UI,自行撰寫註冊使用者網頁,但仍不完整,因User與Profile是一對一關係,故註冊時應合併兩者所有欄位進行註冊,Grails對於處理這樣的註冊表單相當方便,也是因為binddata的觀念,再來則是Grails中如何上傳圖片,將以另外一個小例子介紹,合併今日的範例就是一個在現有的Domain class(User, Profile)之下完整的使用者註冊表單。
其實我們的userController無需做任何code的修改關鍵在於昨日程式碼中的這一行

def user=new User(params)
//params是map,以當作constructor參數建立一個新的user物件

建立一個新的user物件,根據User的定義當然包含以下屬性:

String userId
	String password
	String personalPage
	Date dateCreated
	
	static hasOne = [profile : Profile]
	//1對1 mapping
	static hasMany = [posts:Post, tags:Tag, following: User]

所以只要params裡有對應之欄位有值,都會存入建立的user物件裡,當然也包括一對一的profile變數,故只要在GSP裡加入profile有關的欄位要求使用者輸入,則將自動new一個Profile物件對應到新的user,故register.gsp中g:form內新增程式碼如下如下,把user.profile.相關欄位屬性加入即可。

<dt>Full Name</dt>
            <dd>
                <g:textField name="profile.fullName" value="${user?.profile?.fullName}"/>
            </dd>

            <dt>Education</dt>
            <dd>
                <g:textField name="profile.education" value="${user?.profile?.education}"/>
            </dd>

            <dt>Email</dt>
            <dd>
                <g:textField name="profile.email" value="${user?.profile?.email}"/>
            </dd>
            
            <dt>city</dt>
            <dd>
                <g:textField name="profile.city" value="${user?.profile?.city}"/>
            </dd>

run-app,原register.gsp網頁顯示如下:

填好送出後,顯示網頁如下:

再來介紹如何上傳圖片,先以一個簡單的uploadform表單為例,故我們新增->ImageController以及uploadform.gsp來處理上傳圖片的相關動作,之前一值忘記提,當我們新增一個controller時,預設導向都是
def index(){}

,以本例來說,我們將直接導向uploadform.gsp, 可參閱等一下的程式碼。

接著要介紹Grals所提供的Command Objects這個物件,這個物件主要的功能在於把網頁表單的資料存在這個Command Object裡,相當將params的參數bind在這個物件的屬性裡,通常命名為xxxCommand,Grails將自動把params的參數值指定給Command Object中屬性,以本例來說我們建立一個PhotoUploadCommand{}物件裡面有userId跟photo兩個屬性。

uploadform.gsp裡我們希望選擇使用者並上傳圖片 code如下:

    <title>Upload Image Test</title>
    <meta name="layout" content="main">


    <h1>Upload user's image</h1>
    <g:uploadForm action="upload">
        <p>User Id:
        <g:select name="userId" from="${userList}" optionKey="userId" optionValue="userId" />
        <p/>
        Photo: <input name="photo" type="file" />
        <g:submitButton name="upload" value="Upload"/>
    </g:uploadForm>

當需要上傳的時候,我們需要使用的是g:uploadForm,另外combobox可以用g:select這個標籤來實作,combobox的值可以從ImageController帶過來,感覺蠻方便的,photo部分則須注意type為file

在ImageController裡,我們必須
1.定義PhotoUploadCommand物件
1.定義uploadform,並提供userlist
2.定義upload這個方法處理照片上傳的動作,傳入參數就如剛剛所提的command object-PhotoUploadCommand,該物件會有表單資料,photo參數則指定給user.profile.photo,完成後導向UseController並將結果顯示於profiles.gsp

我們先來看profile.gsp,該網頁將顯示該使用者相片以及相關資料,code如下

    <title>${profile.fullName} Profile</title>
    <meta name="layout" content="main"/>
    <style>
    .profilePic {
        border: 1px dotted gray;
        background: lightyellow;
        padding: 1em;
        font-size: 1em;
    }
    </style>



    <div class="profilePic">
        <g:if test="${profile.photo}">
            <img src="${createLink(controller:'image', action: 'showImage', id: profile.user.userId)}"/>
        </g:if>
        <p>Profile for <strong>${profile.fullName}</strong></p>
        <p>Education: ${profile.education}</p>
        <p>Email: ${profile.email}</p>
        <p>City: ${profile.city}</p>
    </div>
      

要顯示圖片於GSP,由於圖片剛剛我們是存在資料庫,所以要將圖片從資料庫撈出來,必須借助creatLink這個方法,Controller指定為ImageController,且還需coding showImage方法以及提供對應之使用者id參數,才能正確顯示圖面

故ImageController完整程式碼如下:

class ImageController {
	
	def index(){
		redirect(action:"uploadform")
		//直接導向uploadform.gsp
	}
	
	def upload(PhotoUploadCommand puc){
		def user = User.where {userId =~ puc.userId}.get()
		user.profile.photo=puc.photo
		//將表單中photo資料指定給user.profile.photo
		redirect(controller:"user",action:'profile', id:puc.userId)
	}
	def uploadform(){
		[userList:User.list()]
		//提供userList
	}
	
	def showImage(String id){
		def user = User.where {userId =~ id}.get()
		if(user?.profile?.photo){
			response.setContentLength(user.profile.photo.size())
			//說實話我不知道為什麼要setContentLength
			response.outputStream.write(user.profile.photo)
			//將圖片畫在網頁上
		}else{
			response.sendError(404)
		}
	}
}

class PhotoUploadCommand{
	String userId //相當於userId=params.userId
	byte[] photo //相當於photo=params.photo
}

從ImageController pass參數到PostController,理所當然,PostController已必須撰寫相對應的code來render profile.gsp,從profile.gsp中尉減少變數長度,不需要在user.profile.xxx,故我們可以指定傳給profile.gsp時就是profile物件,故於UserController必須新增程式碼如下:

def profile(String id){
	   def user=User.where {userId =~ id}.get()
	   if(!user){
	   		response.sendError(404)
	   }else{
	   	    [profile:user.profile]	
			//直接提供profile物件
	   }
	     
   }

還有一點說明,其實這過程當中,Server都不須重新啟動,當有檔案更新時,Grails將自動complile,立即生效的,截圖如下:

測試上傳圖面網頁如下:

新增成功網頁如下:

最後想跟大家分享的是,即便有書邊看邊學,看似容易,但也是需要時間try跟debug,原本以為很容易的事情,其實都是需要時間的,例如今天我的GGTS的console就是這個樣子....即便今日的範例很簡單

| Loading Grails 2.2.3
| Configuring classpath.
| Environment set to development.....
| Packaging Grails application....
| Compiling 1 source files.....
| Running Grails application十月 18, 2013 9:22:15 下午 org.apache.coyote.AbstractProtocol init
資訊: Initializing ProtocolHandler ["http-bio-8080"]
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.StandardService startInternal
資訊: Starting service Tomcat
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.StandardEngine startInternal
資訊: Starting Servlet Engine: Apache Tomcat/7.0.39
十月 18, 2013 9:22:15 下午 org.apache.catalina.startup.ContextConfig getDefaultWebXmlFragment
資訊: No global web.xml found
十月 18, 2013 9:22:15 下午 org.apache.catalina.core.ApplicationContext log
資訊: Initializing Spring root WebApplicationContext

| Server running. Browse to http://localhost:8080/JasonMicroBlog
| Compiling 1 source files.
| Error 2013-10-18 21:23:28,849 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver  - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
    Line | Method
->>  464 | doFilter  in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by NullPointerException: Cannot get property 'profile' on null object
->>    3 | doCall    in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     13 | run       in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:25:06,806 [http-bio-8080-exec-4] ERROR errors.GrailsExceptionResolver  - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
    Line | Method
->>  464 | doFilter  in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by NullPointerException: Cannot get property 'profile' on null object
->>    3 | doCall    in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     13 | run       in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:25:46,979 [http-bio-8080-exec-8] ERROR errors.GrailsExceptionResolver  - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/user/profile/Jason
Cannot get property 'profile' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
    Line | Method
->>  464 | doFilter  in \grails-app\views\user\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GroovyPagesException: Error evaluating expression [user.profile.fullName] on line [3]: Cannot get property 'profile' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/user/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by NullPointerException: Cannot get property 'profile' on null object
->>    3 | doCall    in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     13 | run       in D__groovy_JasonMicroBlog_grails_app_views_user_profile_gsp
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 21:29:20,689 [http-bio-8080-exec-1] ERROR errors.GrailsExceptionResolver  - NullPointerException occurred when processing request: [GET] /JasonMicroBlog/image/profile/Jason
Cannot get property 'fullName' on null object. Stacktrace follows:
Message: Error processing GroovyPageView: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
    Line | Method
->>  464 | doFilter  in \grails-app\views\image\profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GrailsTagException: Error executing tag <sitemesh:wrapTitleTag>: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/image/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by GroovyPagesException: Error evaluating expression [profile.fullName] on line [3]: Cannot get property 'fullName' on null object
->>    3 | doCall    in D:/groovy/JasonMicroBlog/grails-app/views/image/profile.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
Caused by NullPointerException: Cannot get property 'fullName' on null object
->>    3 | doCall    in D__groovy_JasonMicroBlog_grails_app_views_image_profile_gsp$_run_closure1_closure3_closure7_closure8
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     13 | run       in D__groovy_JasonMicroBlog_grails_app_views_image_profile_gsp
|    195 | doFilter  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter  in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker in java.util.concurrent.ThreadPoolExecutor
|    615 | run       in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 22:03:15,081 [Thread-9] ERROR compiler.GrailsProjectWatcher  - Compilation Error: startup failed:
D:\groovy\JasonMicroBlog\grails-app\controllers\com\JasonMicroBlog\UserController.groovy: 63: expecting '}', found ')' @ line 63, column 38.
   	   def user=User.where {userId =~ id)}.get()
                                        ^
1 error
| Compiling 1 source files.....
| Compiling 1 source files.
| Error 2013-10-18 22:06:02,415 [http-bio-8080-exec-8] ERROR errors.GrailsExceptionResolver  - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
    Line | Method
->>  837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     21 | showImage       in com.JasonMicroBlog.ImageController$$EOKd0t0A
|    195 | doFilter . . .  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter        in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
|    615 | run             in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . . . . in java.lang.Thread
| Error 2013-10-18 22:06:11,456 [http-bio-8080-exec-9] ERROR errors.GrailsExceptionResolver  - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
    Line | Method
->>  837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     21 | showImage       in com.JasonMicroBlog.ImageController$$EOKd0t0A
|    195 | doFilter . . .  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter        in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
|    615 | run             in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . . . . in java.lang.Thread
| Error 2013-10-18 22:21:31,084 [http-bio-8080-exec-6] ERROR errors.GrailsExceptionResolver  - MissingPropertyException occurred when processing request: [GET] /JasonMicroBlog/image/showImage/Jason
No such property: photo for class: grails.gorm.DetachedCriteria. Stacktrace follows:
Message: No such property: photo for class: grails.gorm.DetachedCriteria
    Line | Method
->>  837 | propertyMissing in grails.gorm.DetachedCriteria
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 
|     21 | showImage       in com.JasonMicroBlog.ImageController$$EOKd0t0A
|    195 | doFilter . . .  in grails.plugin.cache.web.filter.PageFragmentCachingFilter
|     63 | doFilter        in grails.plugin.cache.web.filter.AbstractFilter
|   1145 | runWorker . . . in java.util.concurrent.ThreadPoolExecutor
|    615 | run             in java.util.concurrent.ThreadPoolExecutor$Worker
^    744 | run . . . . . . in java.lang.Thread
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.....
| Compiling 1 source files.

Controller的部分到今天暫告一段落,明天開始會跟大家分享view的部分,


上一篇
Grails-Service概念 and DataBind方法介紹
下一篇
Grails-UrlMapping設定、Grails Tag以及Grails layout(sitemesh)介紹
系列文
邊看邊學Groovy/Grails/Gradle27

尚未有邦友留言

立即登入留言