程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> 精通Grails: 改變Grails應用程序的外觀

精通Grails: 改變Grails應用程序的外觀

編輯:關於JAVA

歡迎閱讀第二年度的 精通 Grails。正如我在 2008 年的最後一篇文章中許諾的一樣,在新的一年將 使用新的應用程序。再見了,Trip Planner!讓我們歡迎 blog 發布系統(blog publishing system)!

我已經將這個應用程序命名為 Blogito。在西班牙語中,它表示 “little blog”,或者是對笛卡兒 的 Cogito ergo sum(“我思故我在”)表示敬意。可從 blogito.org 下載這個完整的應用程序。在接 下來的幾篇文章中,您將一步步構建核心的功能。

這篇文章的重點是顯著地更改 Grails 應用程序的外觀。去年的 Trip Planner 的外觀很怪異,恐怕 只有開發人員才會喜歡(說句公道話,與外觀相比,我對核心功能更感興趣)。在本文中,通過使用一些 CSS 和局部模板進行調整,將得到一個外觀新穎的 Grails 應用程序。在這個過程中,您還可以簡單溫習 一下 Grails 特性,比如 scaffold、自動時間戳、修改默認模板、創建自定義 TagLib,以及調整關鍵配 置文件(比如 Bootstrap.groovy 和 URLMapper.groovy)。

在開始之前,必須安裝 Grails 1.1。撰寫本文時,它還是 beta 版。

安裝 Grails 1.1

Grails 在 Java 1.5 或 1.6 上運行表現最佳。通過命令提示符輸入 java -version,確保 Java 版 本是比較新的。

Java 1.5 或 1.6 就緒之後,安裝 Grails 的步驟就很簡單了:

從 Grails 站點 下載 grails.zip 文件。

解壓縮 grails.zip。

創建一個 GRAILS_HOME 環境變量。

將 GRAILS_HOME/bin 添加到 PATH。

如果您使用的應用程序是使用上一版本的 Grails 編寫的,則可以輸入 grails upgrade 將其遷移到 最新的版本。但如果需要處理多個版本的 Grails,應該怎麼辦呢?

如果運行的是 UNIX®-esque OS(UNIX、Linux®,或 OS X)系統,通過將 $GRAILS_HOME 環 境變量指向 symlink 就可以輕松處理 Grails 的多個版本。在我的系統上,將 GRAILS_HOME 指向 /opt/grails。這個步驟完成之後,通過快捷的 ln -s 就可以在各個版本之間切換,如清單 1 所示:

清單 1. 為 UNIX、Linux 或 Mac OS X 系統上的 $GRAILS_HOME 創建一個 symlink

        
$ ln -s /opt/grails-1.1-beta1 grails
$ ls -l | grep "grails"
lrwxr-xr-x  1 sdavis admin    17 Dec 5 11:12 grails - > grails-1.1-beta1/
drwxr-xr-x 14 sdavis admin    476 Nov 10 2006 grails- 0.3.1
drwxr-xr-x 16 sdavis admin    544 Feb 9 2007 grails-0.4.1
drwxr-xr-x  17 sdavis admin    578 Apr 6 2007 grails-0.4.2
drwxr-xr-x 17 sdavis admin     578 Jun 15 2007 grails-0.5
drwxr-xr-x 19 sdavis admin    646 Jul 30 2007 grails-0.5.6
drwxr-xr-x 18 sdavis admin    612 Sep 18 2007 grails-0.6
drwxr -xr-x 19 sdavis admin    646 Feb 19 2008 grails-1.0
drwxr-xr-x 18 sdavis admin     612 Apr 5 2008 grails-1.0.2
drwxr-xr-x 18 sdavis admin    612 Oct 9 21:46 grails-1.0.3
drwxr-xr-x 18 sdavis admin    612 Nov 24 20:43 grails-1.0.4
drwxr-xr-x 18 sdavis admin    612 Dec 5 11:13 grails-1.1-beta1

在 Windows® 系統上,最好是直接更改 %GRAILS_HOME% 變量。在變更之後,不要忘記重新啟動現有的命 令提示符。

輸入 grails -version 以確保使用了最新的版本,並且正確設置了 GRAILS_HOME 變量。現在,輸入 應該如清單 2 所示:

清單 2. grails -version 的輸出結果

$ grails -version
Welcome to Grails 1.1-beta2 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /opt/grails

現在 Grails 1.1 已經安裝完成,可以創建新的應用程序了。

創建應用程序

輸入 grails create-app blogito 以生成初始的目錄結構。轉到新的 blogito 目錄並輸入 grails create-domain-class Entry,以創建表示 blog 條目的類。在 grails-app/domain 找到 Entry.groovy ,並添加清單 3 中的代碼:

清單 3. 創建 Entry 類

class Entry {
  static constraints = {
   title()
   summary(maxSize:1000)
   dateCreated()
   lastUpdated()
  }

  String title
  String summary
  Date dateCreated
  Date lastUpdated
}

每個 Entry 有一個 title 和 summary 字段。將 maxSize 限制范圍設置為 1,000 個字符,這會導致 動態地構造 HTML 表單,從而為 summary 字段提供文本區域(而不是簡單的文本字段)。

記住,dateCreated 和 lastUpdated 是 Grails 中比較神奇的字段名。這些時間戳字段非常適合 blog 應用程序 — 它們允許在列表的頂部保留最新的 Entry。

在域類准備就緒之後,下一步就是創建一個控制器。輸入 grails create-controller Entry。將清單 4 中的代碼添加到 grails-app/controllers/EntryController.groovy:

清單 4. 創建 EntryController

class EntryController {
   def scaffold = Entry
}

表面上看起來很簡單的 def scaffold = Entry 行指示 Grails 為 Entry 類構造其余的支持。您隨後 將獲得一個條目表,其中 Entry 類中的每個字段都有一個列(以及一個主鍵 ID 字段和一個樂觀鎖定的 版本字段)。您還獲得完整的 Groovy 服務器頁面(Groovy Server Pages,GSP),它們提供很普通但至 關重要的 Create/Retrieve/Update/Delete (CRUD) 功能。

輸入 grails run-app 並通過 Web 浏覽器訪問 http://localhost:8080/blogito。單擊 EntryController,然後單擊 New Entry。這樣做的好處是所有 Entry 字段都出現在創建表單中(如圖 1 所示)。但這也有不好的地方 — 用戶不應該處理這些時間戳字段。您需要調整默認的模板來解決這個問 題。

圖 1. Create Entry 表單中可編輯的時間戳字段

調整默認模板

您可以輸入 grails generate-views Entry 手動地從 GSP 文件中刪除 dateCreated 和 lastUpdated 字段,但這不能從根本上解決問題。您可能希望這些字段永遠不出現在創建和編輯表單中。最好是在 def scaffold 中更改模板。

輸入 grails install-templates。在 src/templates/scaffolding 中查找 create.gsp 和 edit.gsp 。在每個文件中,將 dateCreated 和 lastUpdated 添加到 excludedProps,如清單 5 所示:

清單 5. 從 list.gsp 和 show.gsp 模板中刪除時間戳字段

excludedProps = ['version',
         'id',
         'dateCreated',
         'lastUpdated',
         Events.ONLOAD_EVENT,
         Events.BEFORE_DELETE_EVENT,
         Events.BEFORE_INSERT_EVENT,
         Events.BEFORE_UPDATE_EVENT]

重啟 Grails,確保時間戳字段不再出現(參見圖 2):

圖 2. 不包含時間戳字段的表單

更改排序的順序

添加新條目時,默認情況下是根據 ID 對表進行排序的。blog 通常以逆時針順序對條目進行排序 — 最新的排在前面。在以前版本的 Grails 中,要更改默認的排序順序,則必須在 EntryController.groovy 中手動編輯列表閉包。在現有的代碼行下面添加兩個排序代碼行並不困難(見 清單 6)。問題是不能再從幕後動態構建這個代碼(可以查找 src/templates/scaffolding/Controller.groovy 或輸入 grails generate-controller Entry 查看默認 的底層實現)。

清單 6. Grails 1.0.x 中的排序

def list = {
   if(!params.max) params.max = 10
   if(!params.sort) params.sort = "lastUpdated"
   if(!params.order) params.order = "desc"
   [ entryList: Entry.list( params ) ]
}

Grails 1.1 將一個很簡單但極為有用的特性添加到靜態映射塊,即 sort。將清單 7 中的映射塊添加 到 Entry.groovy。通過在域類中處理排序,您可以繼續對控制器執行 def scaffold 操作。

清單 7. 將 sort 添加到 static mapping 塊

class Entry {
  static constraints = {
   title()
   summary(maxSize:1000)
   dateCreated()
   lastUpdated()
  }

  static mapping = {
   sort "lastUpdated":"desc"
  } 

  String title
  String summary
  Date dateCreated
  Date lastUpdated
}

重啟 Grails,確保編輯後的條目移動到列表的頂端,如圖 3 所示:

圖 3. 驗證新的排序順序

在開發模式下創建偽記錄

每次重啟 Grails 時將丟失現有的條目,您注意到了嗎?記住,這是一個特性,而不是 bug。在每次 啟動 Grails 時將創建條目表,並且在關閉 Grails 時刪除它們。打開 grails- app/conf/DataSource.groovy 驗證這個特性。很明顯,開發模式中的 db-create 值設置為 create-drop 。

可以將該值更改為 update,但這也不是很理想。在開發過程的前期,模式是很不穩定的 — 您可以隨 時添加或刪除字段,或修改限制條件等等。在所有東西穩定下來之前,我覺得最好將 db-create 設置為 create-drop。

在開發模式中經常要重新輸入樣例數據,為了使這個操作沒那麼繁瑣,可以為 grails- app/conf/BootStrap.groovy 添加一些邏輯。清單 8 中的代碼在 Grails 每次啟動時插入新的記錄:

清單 8. 在開發模式中添加偽記錄

import grails.util.GrailsUtil
class BootStrap {
  def init = { servletContext ->
   switch(GrailsUtil.environment){
    case "development":
     new Entry(
      title:"Grails 1.1 beta is out", summary:"Check out the new features").save()
     new Entry(
      title:"Just Released - Groovy 1.6 beta 2", summary:"It is looking good.").save ()
    break
    case "production":
    break
   }
  }
  def destroy = {
  }
}

再次重啟 Grails。這一次,條目表中將出現現有的記錄,如圖 4 所示:

圖 4. 在引導時出現的偽記錄

改善列表的外觀

列表視圖中的默認 HTML 表對入門人員已經足夠好,但對 Blogito 而言,這明顯不是長期解決辦法。 blog 頁面通常垂直地顯示 date、title 和 summary 字段,而不是橫向地顯示(每次顯示一個字段)。

為進行這種更改,輸入 grails generate-views Entry。前面動態構造的 GSP 文件現在應該出現在 grails-app/views/entry 中。在文本編輯器中打開 list.gsp。在頭部將標題從 Entry List 更改為 Blogito。刪除 <h1> 和 <g:if> 塊,然後用清單 9 中的代碼代替現有的 <div class="list">。

清單 9. 更改 list.gsp 視圖

<div class="list">
<g:each in="${entryInstanceList}" status="i" var="entryInstance">
  <div class="entry">
  <span class="entry-date">${entryInstance.lastUpdated}</span>
  <h2><g:link action="show" id="${entryInstance.id}">${entryInstance.title} </g:link></h2>
  <p>${entryInstance.summary}</p>
  </div>
</g:each>
</div>

注意,這些代碼是經過大大簡化的。可以刪除 <fieldValue> 標記 — 它們幫助將域類綁定到 HTML 表單字段,但在這裡沒有實用價值。每個 Entry 都包含在一個指定的 <div> 中,而 lastUpdated 字段則包含在指定的 <span> 中。這些類屬性連接到隨後將構建的 CSS 格式中。 title 和 summary 字段包含在普通的 HTML 頭部和段落標記中。

在浏覽器中刷新列表視圖(見圖 5)。這還不算是進步。但是添加一些新的 CSS 指令之後,它的外觀 將有很大的改善。

圖 5. 沒有使用 CSS 的新列表

將清單 10 中的 CSS 添加到 web-app/css/main.css 的底部:

清單 10. list.gsp 視圖的 CSS 自定義

/* Blogito customizations */
.entry {
  padding-bottom: 2em;
}
.entry-date {
  color: #999;
}

再次刷新浏覽器將看到更加好看的外觀(見圖 6)。現在還沒有充分利用 CSS,但是已經擁有一個好 的起點。

圖 6. 帶有 CSS 的新列表

創建 Date TagLib

現在,需要使 lastUpdated 日期外觀更加友好。最好將可重用代碼片段放在自定義 TagLib 中。輸入 grails create-tag-lib Date。將清單 11 中的代碼添加到 grails-app/taglib/DateTagLib.groovy:

清單 11. 針對 DateTagLib 的代碼

import java.text.SimpleDateFormat
class DateTagLib {
  def longDate = {attrs, body ->
   //parse the incoming date
   def b = attrs.body ?: body()
   def d = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").parse(b)

   //if no format attribute is supplied, use this
   def pattern = attrs["format"] ?: "EEEE, MMM d, yyyy"
   out << new SimpleDateFormat(pattern).format(d)
  }
}

現在,將 lastUpdated 字段包含在 grails-app/views/entry/list.gsp 中剛才創建的 <g:longDate> 標記中,如清單 12 所示:

清單 12. 在 list.gsp 中使用 <g:longDate>

<div class="entry">
  <span class="entry-date"><g:longDate>${entryInstance.lastUpdated} </g:longDate></span>
  <h2>${entryInstance.title}</h2>
  <p>${entryInstance.summary}</p>
</div>

重啟 Grails 並刷新 Web 浏覽器。您將看到日期的新格式,如圖 7 所示:

圖 7. 使用自定義 <g:longDate> 標記創建的新日期格式

創建局部模板

這個布局非常漂亮。我打算在 show.gsp 中重用它。在 grails-app/views/entry 中創建 _entry.gsp ,並添加清單 13 中所示的代碼(當然,可以從 list.gsp 剪切粘貼過來)。

清單 13. 針對 _entry.gsp 的代碼

<div class="entry">
<span class="entry-date"><g:longDate>${entryInstance.lastUpdated} </g:longDate></span>
<h2><g:link action="show" id="${entryInstance.id}">${entryInstance.title} </g:link></h2>
<p>${entryInstance.summary}</p>
</div>

為了使用剛才創建的局部模板,需要像清單 14 那樣調整 list.gsp:

清單 14. 在 list.gsp 中使用 _entry.gsp 局部模板

<div class="list">
  <g:each in="${entryInstanceList}" status="i" var="entryInstance">
   <g:render template="entry" bean="${entryInstance}" var="entryInstance" />
  </g:each>
</div>

現在還可以在 list.gsp 中重用局部模板,如清單 15 所示:

清單 15. 在 show.gsp 中使用 _entry.gsp 局部模板

<div class="body">
  <g:render template="entry" bean="${entryInstance}" var="entryInstance" />
  <div class="buttons">
   <!-- snip -->
  </div>
</div>

在浏覽器中刷新列表視圖。它將和前面完全一樣。現在單擊條目的標題,確保它也適用於這個視圖。

自定義頭部

各個部分將協調地顯示。現在需要用自己的標志來代替 Grails 標志。

我沒有看到在 list.gsp 或 show.gsp 的其他地方引用了 Grails 徽標。記住,Grails 使用 SiteMesh 將最終頁面的不同部分結合起來。查看 grails-app/views/layouts/main.gsp 就會看到包含 grails_logo.jpg 文件的位置。

在 grails-app/views/layouts 中創建另一個名為 _header.gsp 的局部模板。添加清單 16 中的代碼 。注意,Blogito 是一個鏈接到主頁的超鏈接。

清單 16. 針對 _header.gsp 局部模板的代碼

<div id="header">
  <p><g:link class="header-main" controller="entry">Blogito</g:link></p>
  <p class="header-sub">A tiny little blog</p>
</div>

現在像清單 17 那樣編輯 main.gsp,以包含 _header.gsp 文件:

清單 17. 使用新 _header.gsp 局部模板的 Main.gsp

<body>
  <div id="spinner" class="spinner" style="display:none;">
   <img src="${createLinkTo(dir:'images',file:'spinner.gif')}" alt="Spinner" />
  </div>
  <g:render template="/layouts/header"/>
  <g:layoutBody />
</body>

最後,再為 web-app/css/main.css 添加一些代碼,如清單 18 所示:

清單 18. _header.gsp 局部模板的 CSS 格式

#header {
  background: #67c;
  padding: 2em 1em 2em 1em;
  margin-bottom: 1em;
}
a.header-main:link, a.header-main:visited {
  color: #fff;
  font-size: 3em;
  font-weight: bold;
}
.header-sub {
  color: #fff;
  font-size: 1.25em;
  font-style: italic;
}

刷新浏覽器查看發生了什麼變化(見圖 8)。單擊條目的標題,然後在頭部單擊 Blogito 導航到主頁 。

圖 8. 展示新的頭部

在登錄之前隱藏導航欄

您還需要處理一個容易弄錯的標志,它表示這是一個 Grails 應用程序:導航欄。盡管我們在下一篇 文章中才進行身份驗證,但是現在可以為未驗證的用戶關閉導航欄。這可以通過將 <div> 包含在 簡單的 <g:if> 測試來實現。這個測試查找存儲在會話范圍中的 user 變量。

像清單 19 那樣修改 list.gsp 和 show.gsp:

清單 19. 在登錄之前隱藏導航欄

<g:if test="${session.user}">
  <div class="nav">
    <span class="menuButton">
     <a class="home" href="${createLinkTo(dir:'')}">Home</a>
    </span>
    <span class="menuButton">
     <g:link class="create" action="create">New Entry</g:link>
    </span>
  </div>
</g:if>

在 show.gsp 中,在按鈕 <div> 的周圍添加相同的測試(您最不願意看到的事情就是用戶編輯 未經驗證或刪除 blog 條目,不是嗎?)。

最後,對 list.gsp 的外觀進行調整。將 paginateButtons <div> 從 body <div> 移出 ,如清單 20 所示。這使導航欄能夠橫跨整個屏幕,從而在屏幕的底部添加一個漂亮的可視錨。

清單 20. 將 paginateButtons <div> 從 body <div> 移出,改善外觀

<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
  <meta name="layout" content="main" />
  <title>Blogito</title>
</head>
<body>
  <g:if test="${session.user}">
  <div class="nav">
   <span class="menuButton">
    <a class="home" href="${createLinkTo(dir:'')}">Home</a>
   </span>
   <span class="menuButton">
    <g:link class="create" action="create">New Entry</g:link>
   </span>
  </div>
  </g:if>
  <div class="body">
   <div class="list">
   <g:each in="${entryInstanceList}" status="i" var="entryInstance">
    <g:render template="entry" bean="${entryInstance}" var="entryInstance" />
   </g:each>
   </div>
  </div>
  <div class="paginateButtons">
   <g:paginate total="${Entry.count()}" />
  </div>
</body>
</html>

再添加一些 CSS,如清單 21 所示,確保 paginateButtons <div> 出現在 body <div> 的底部,而不是旁邊:

清單 21. 確保 paginateButtons <div> 出現在屏幕底部的 CSS

.paginateButtons{
  clear: left;
}

最後一次刷新浏覽器。您的屏幕應該如圖 9 所示:

圖 9. 隱藏導航欄

設置主頁

現在,一切准備就緒了,此時應該將 EntryController 設置為默認主頁。為此,需要添加一個將 /( URL http://localhost:9090/blogito/ 中的尾部反斜槓)重新定向到 EntryController 的映射。根據清 單 22 編輯 grails-app/conf/UrlMappings.groovy:

清單 22. 將 EntryController 設置為默認主頁

class UrlMappings {
   static mappings = {
    "/$controller/$action?/$id?"{
       constraints {
          // apply constraints here
        }
     }
     "/"(controller:"entry")
     "500"(view:'/error')
  }
}

結束語

本文的目標是顯示如何改變 Grails 應用程序的外觀。僅需幾行 CSS 就可以改變顏色、字體和塊元素 周圍的空間。通過局部模板和 TagLibs 可以創建一些可重用的代碼片段。最後,您還可以利用 Grails 框架的所有優點,並且獲得一個擁有獨特外觀的應用程序。

下一期文章繼續探討 Blogito 應用程序。您將添加一個 User 域類,從而讓多個人添加 blog 條目。 此外,您還將研究 Grails 編解碼器,並且進一步了解自定義 URL 映射。不要忘記可以通過 http://blogito.org 下載完整的應用程序。到那時,就可以享受精通 Grails 帶來的樂趣了。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved