6.2 Groovy Server Pages
Groovy Servers Pages (或者简写为 GSP)Grails的视图技术。它被设计成像ASP和JSP这样被使用者熟悉的技术,但更加灵活和直观.
GSP存在于Grails的grails-app/views
目录中,他们通常会自动渲染(通过规约),或者像这样通过render方法:
render(view:"index")
GSP使典型的混合标记和GSP标签,辅助页面渲染.
虽然,它可能会在你的GSP页面中内置Groovy逻辑,Although it is possible to have Groovy logic embedded in your GSP and doing this will be covered in this document the practice is strongly discouraged. Mixing mark-up and code is a bad thing and most GSP pages contain no code and needn't do so.
一个GPS通常拥有一个"model",它是变量集被用于视图渲染。通过一个控制器model被传递到GSP视图。例如,考虑下列控制器的操作:
def show = { [book: Book.get(params.id)] }
这个操作将查找一个book
实体,并创建一个包含关键字为Book
的model,这个关键字可在随后的GSP视图中应用:
<%=book.title%>
6.2.1 GSP基础
在下一节,我们将通过GSP基础知识让你知道它能做什么。首先,我们将涵盖基础语法,对于JSP和ASP用户是非常熟悉的.
GSP支持使用<% %>
来嵌入Groovy代码(这是不推荐的):
<html> <body> <% out << "Hello GSP!" %> </body> </html>
同样,你可以使用<%= %>
语法来输出值:
<html> <body> <%="Hello GSP!" %> </body> </html>
GSP同样支持服务器端JSP样式注释,像下列示例显示的这样:
<html> <body> <%-- This is my comment --%> <%="Hello GSP!" %> </body> </html>
6.2.1.1 变量与作用域
在 <% %>
中你当然可以声明变量:
<% now = new Date() %>
然后,在页面中的之后部分可以重复使用 :
<%=now%>
然而, 在GSP中存在着一些预先定义的变量,包括:
application
- javax.servlet.ServletContext 实例applicationContext
Spring ApplicationContext 实例flash
- flash 对象grailsApplication
- GrailsApplication 实例out
- 响应输出流params
- params 对象用于检索请求参数request
- HttpServletRequest 实例response
- HttpServletResponse 实例session
- HttpSession 实例webRequest
- GrailsWebRequest 实例
6.2.1.2 逻辑和迭代
使用 <% %>
语法,你当然可以使用这样的语法进行嵌套循环等等操作:
<html> <body> <% [1,2,3,4].each { num -> %> <p><%="Hello ${num}!" %></p> <%}%> </body> </html>
同样可以分支逻辑:
<html> <body> <% if(params.hello == 'true' )%> <%="Hello!"%> <% else %> <%="Goodbye!"%> </body> </html>
6.2.1.3 页面指令
GSP同样支持少许的JSP样式页面指令.
import指令允许在页面中导入类。然而,它却很少被使用,因为Groovy缺省导入和GSP 标签:
<%@ page import="java.awt.*" %>
GSP同样支持contentType@ 指令:
<%@ page contentType="text/json" %>
contentType@指令允许GSP使用其他的格式来渲染.
6.2.1.4 表达式
尽管GSP也支持 <%= %>
语法,而且很早就介绍过,但在实际当中却很少应用,因为此用法主要是为ASP和 、JSP开发者所保留的。 而GSP的表达式跟JSP EL表达式很相似的,跟Groovy GString的 ${expr}
用法也很像:
<html> <body> Hello ${params.name} </body> </html>
尽管如此,跟JSP EL不同的是, 你可以在${..}
括号中使用Groovy表达式.${..}
中的变量缺省情况下是非转义,因此变量的任何HTML字符串内容被直接输出到页面,要减少这种Cross-site-scripting (XSS)攻击的风险, 你可以设置grails-app/conf/Config.groovy
中的grails.views.default.codec
为HTML转化方式:
grails.views.default.codec='html'
其他可选的值是'none' (缺省值)和'base64'.
6.2.2 GSP标签
现在,JSP遗传下来的缺点已经被取消,下面的章节将涵盖GSP的内置标签,它是定义GSP页面最有利的方法.
标签库 部分涵盖怎么添加你自己的定制标签库.
所有GSP内置标签以前缀g:开始。 不像JSP,你不需要指定任何标签库的导入.假如,一个标签以g:
开始,它被自动认为是一个GSP标签.一个GPS标签的示例看起来像这样:
<g:example />
GSP标签同样可以拥有主体,像这样:
<g:example> Hello world </g:example>
表达式被传递给GSP标签属性,假如没有使用表达式,将被认为是一个String值:
<g:example attr="${new Date()}"> Hello world </g:example>
Maps同样能被传递给GSP标签属性,通常使用一个命名参数样式语法:
<g:example attr="${new Date()}" attr2="[one:1, two:2, three:3]"> Hello world </g:example>
注意,对于String类型属性值,你必须使用单引号:
<g:example attr="${new Date()}" attr2="[one:'one', two:'two']"> Hello world </g:example>
在介绍完基本的语法之后,下面我们来讲解Grails中默认提供的标签.
6.2.2.1 变量与作用域
变量可以在GSP中使用 set 标签来定义:
<g:set var="now" value="${new Date()}" />
这里, 我们给GSP表达式结果赋予了一个名为now
的变量 (简单的构建一个新的 java.util.Date 实体)。 你也可以在<g:set>
主体中定义一个变量:
<g:set var="myHTML"> Some re-usable code on: ${new Date()} </g:set>
变量同样可以被放置于下列的范围内:
page
- 当前页面范围 (默认)request
- 当前请求范围flash
- flash 作用域,因此它可以在下一次请求中有效session
- 用户session范围application
- 全局范围.
选择变量被放入的范围可以使用scope
属性:
<g:set var="now" value="${new Date()}" scope="request" />
6.2.2.2 逻辑和迭代
GSP同样支持迭代逻辑标签,逻辑上通过使用 if , , else 和 elseif 来支持典型的分支情形。 :
<g:if test="${session.role == 'admin'}"> <%-- show administrative functions --%> </g:if> <g:else> <%-- show basic functions --%> </g:else>
<g:each in="${[1,2,3]}" var="num"> <p>Number ${num}</p> </g:each><g:set var="num" value="${1}" /> <g:while test="${num < 5 }"> <p>Number ${num++}</p> </g:while>
6.2.2.3 搜索和过滤
假如你拥有对象集合,你经常需要使用一些方法来排序和过滤他们。 GSP支持 findAll 和 grep 来做这些工作:
Stephen King's Books: <g:findAll in="${books}" expr="it.author == 'Stephen King'"> <p>Title: ${it.title}</p> </g:findAll>
expr
属性包含了一个Groovy表达式,它可以被当作一个过滤器来使用。谈到过滤器,grep标签通过类来完成与过滤器类似的工作:
<g:grep in="${books}" filter="NonFictionBooks.class"> <p>Title: ${it.title}</p> </g:grep>
或者使用一个正则表达式:
<g:grep in="${books.title}" filter="~/.*?Groovy.*?/"> <p>Title: ${it}</p> </g:grep>
上面的示例同样有趣,因为它使用了GPath.Groovy的GPath等同与XPath语言。实际上books集合是books
集合的实体。不过,假设每个books
拥有一个title
,你可以使用表达式books.title
来获取Book titles的list!
6.2.2.4 链接和资源
GSP还拥有特有的标签来帮助你管理连接到控制器和操作. link 标签允许你指定控制器和操作配对的名字,并基于 URL Mappings 映射来自动完成连接。即使你去改变!一些 link 的示例如下:
<g:link action="show" id="1">Book 1</g:link> <g:link action="show" id="${currentBook.id}">${currentBook.name}</g:link> <g:link controller="book">Book Home</g:link> <g:link controller="book" action="list">Book List</g:link> <g:link url="[action:'list',controller:'book']">Book List</g:link> <g:link action="list" params="[sort:'title',order:'asc',author:currentBook.author]"> Book List </g:link>
6.2.2.5 表单和字段
表单基础
GSP支持许多不同标签来帮助处理HTML表单和字段,最基础的是form标签, form标签是一个控制器/操作所理解的正规的HTML表单标签版本。 url
属性允许你指定映射到哪个控制器和操作:
<g:form name="myForm" url="[controller:'book',action:'list']">...</g:form>
我们创建个名为myForm
的表单,它被提交到 BookController
的list
操作。除此之外,适用于所有不同的HTML属性.
表单字段
同构造简单的表单一样,GSP支持如下不同字段类型的定制:
- textField - 'text'类型输入字段
- checkBox - 'checkbox'类型输入字段
- radio - 'radio'类型输入字段
- hiddenField - 'hidden'类型输入字段
- select - 处理 HTML 选择框
上面的每一个都允许GSP表达式作为值:
<g:textField name="myField" value="${myValue}" />
GSP同样包含上面标签的扩张助手版本, 比如radioGroup (创建一组radio标签), localeSelect, currencySelect 和timeZoneSelect(选择各自的地区区域, 货币 和时间区域). .
多样的提交按钮
处理多样的提交按钮这样由来已久的问题,同样可以通过Grails的actionSubmit 标签优雅的处理。它就像一个正规提交,但是,允许你指定一个可选的操作来提交:
<g:actionSubmit value="Some update label" action="update" />
6.2.2.6 标签作为方法调用
GSP标签和其他标签技术一个主要不同在于,来自 controllers(控制器) , 标签库 或者GSP 视图中的GPS标签可以被当作任意的正规标签或者当作方法被调用.
来自GSPs中的标签当作方法调用
当作为方法被调用时,标签的返回值被当作String实体直接被写入响应中。 因此,示例中的createLinkTo能等同的看做方法调用:
Static Resource: ${createLinkTo(dir:"images", file:"logo.jpg")}
当你必须在一个属性内使用一个标签时是特别有用的:
<img src="${createLinkTo(dir:'images', file:'logo.jpg')}" />
I在视图技术中,标签内嵌套标签的特性是不被支持的,这样变得十分混乱,往往使得像Dreamweaver这样WYSWIG的工具产生不利的效果以至于在渲染标签时:
<img src="<g:createLinkTo dir="images" file="logo.jpg" />" />
来自控制器(Controllers)和标签库的标签作为方法调用
你同样可以调用来自控制器和标签库的标签。标签可以不需要内部默认的g:
namespace前缀来调用,并返回String结果:
def imageLocation = createLinkTo(dir:"images", file:"logo.jpg")
然而,你同样可以用命名空间前缀来避免命名冲突:
def imageLocation = g.createLinkTo(dir:"images", file:"logo.jpg")
假如你有一个自定义命名空间,,你可以使用它的前缀来替换(例如,使用 FCK Editor plugin:
def editor = fck.editor()
6.2.3 视图(View)与模板(Templates)
除了views之外, Grails还有模板的概念. 模板有利于分隔出你的视图在可维护的块中,并与 Layouts 结合提供一个高度可重用机制来构建视图.
模板基础
Grails使用在一个视图名字前放置一个下划线来标识为一个模板的规约。 例如,你可能有个位于grails-app/views/book/_bookTemplate.gsp
的模板处理渲染Books:
<div class="book" id="${book?.id}"> <div>Title: ${book?.title}</div> <div>Author: ${book?.author?.name}</div> </div>
为了渲染来自grails-app/views/book
视图中的一个模板,你可以使用render标签:
<g:render template="bookTemplate" model="[book:myBook]" />
注意,我们是怎么样使用render标签的model属性来使用传入的一个model
。假如,你有多个Book
实体,你同样可以使用render标签为每个Book
渲染模板 :
<g:render template="bookTemplate" var="book" collection="${bookList}" />
共享模板
在早先的示例中,我们有一个特定于BookController
模板,它的视图位于grails-app/views/book
.然而,你可能想横跨你的应用来共享模板。
在这种情况下,你可以把他们放置于grails-app/views视图根目录或者位于这个位置的任何子目录,然后在模板属性在模板名字之前使用一个 /
来指明相对模板路径 .例如,假如你有个名为grails-app/views/shared/_mySharedTemplate.gsp
模板, 你可以像下面这样引用它:
<g:render template="/shared/mySharedTemplate" />
你也可以使用这个技术从任何视图或控制器(Controllers)来引用任何目录下的模板:
<g:render template="/book/bookTemplate" model="[book:myBook]" />
模板命名空间
因为模板使用如此频繁,它有一个模板命名空间, 名为tmpl
, 他使模板的使用变得容易. 考虑下面例子的使用模式:
<g:render template="bookTemplate" model="[book:myBook]" />
这个想下面这样通过tmpl
命名空间表示 :
<tmpl:bookTemplate book="${myBook}" />
在控制器(Controllers)和标签库中的模板
你同样可以使用控制器 render方法渲染模板控制器中,它对Ajax引用很有用:
def show = {
def b = Book.get(params.id)
render(template:"bookTemplate", model:[book:b])
}
在控制器(controller)中的render 方法最普通的行为是直接写入响应。 假如,你需要获得模板作为一个String的结果作为替代,你可以使用render标签:
def show = { def b = Book.get(params.id) String content = g.render(template:"bookTemplate", model:[book:b]) render content }
注意, g.
命名空间的用法,它告诉Grails我们想使用标签作为方法调用来代替render 方法.
6.2.4 使用Sitemesh布局
创建布局
Grails利用了Sitemesh,一个装饰引擎,来支持视图布局。布局位于grails-app/views/layouts
目录中。一个典型的布局如下:
<html> <head> <title><g:layoutTitle default="An example decorator" /></title> <g:layoutHead /> </head> <body οnlοad="${pageProperty(name:'body.onload')}"> <div class="menu"><!--my common menu goes here--></menu> <div class="body"> <g:layoutBody /> </div> </div> </body> </html>
关键的元素是layoutHead, layoutTitle和layoutBody标签的用法,这里是他们所做的:
layoutTitle
- 输出目标页面的titlelayoutHead
- 输出目标页面head标签内容layoutBody
- 输出目标页面body标签内容
上面的示例同样表明pageProperty tag 可被用于检查和返回目标页面的外观.
启用布局
这里有一些方法来启用一个布局.简单的在视图中添加meta标签:
<html> <head> <title>An Example Page</title> <meta name="layout" content="main"></meta> </head> <body>This is my content!</body> </html>
在这种情况下,一个名为grails-app/views/layouts/main.gsp
将被用于布局这个页面。假如,我们使用来自早前部分的布局,输出看上去像下列这样:
<html> <head> <title>An Example Page</title> </head> <body οnlοad=""> <div class="menu"><!--my common menu goes here--></div> <div class="body"> This is my content! </div> </body> </html>
在控制器(Controller)中指定布局
另一种用于指定布局的方式是通过在控制器(controller)中为 "layout"属性指定布局的名字, 假如你有个这样的控制器(controller):
class BookController { static layout = 'customer'def list = { … } }
你可以创建一个grails-app/views/layouts/customer.gsp
布局,应用于所有 BookController
中委派的视图 . "layout"属性值可能包含相对于grails-app/views/layouts/
目录的路径结构 . 例如:
class BookController { static layout = 'custom/customer'def list = { … } }
视图的显然可通过 grails-app/views/layouts/custom/customer.gsp
模板.
布局规约
第二种关联布局的方法是使用"布局规约",假如你有个这样的控制器:
class BookController { def list = { … } }
你可以创建一个名为grails-app/views/layouts/book.gsp
的布局,根据规约,它将被应用于BookController
的所有视图中。
换句话说,你可以创建一个名为grails-app/views/layouts/book/list.gsp
的布局,它将只被应用于BookController
中的list
操作,
如果你同时使用了以上提到的两种布局的话,那当list操作被执行的时候,那么操作将根据优先级的顺序来使用布局.
内联布局
通过applyLayout标签Grails同样支持Sitemesh的内联布局概念。 applyLayout
标签可以被用于应用一个布局到一个模板,URL或者内容的任意部分。事实上,通过"decorating"你的模板允许你更进一步的积木化你的视图结构.
一些使用示例如下:
<g:applyLayout name="myLayout" template="bookTemplate" collection="${books}" /><g:applyLayout name="myLayout" url="http://www.google.com" />
<g:applyLayout name="myLayout"> The content to apply a layout to </g:applyLayout>
Server-Side包含
当 applyLayout标签被以用于引用布局外内容 applying layouts to , 假如你想简单的在当前页面包含外部内容,你可以使用 include:
<g:include controller="book" action="list"></g:include>
你甚至可以结合 include 标签和 applyLayout 标签来添加灵活性:
<g:applyLayout name="myLayout"> <g:include controller="book" action="list"></g:include> </g:applyLayout>
最后,你也可以在控制器(controller)或标签库把include标签作为方法调用 :
def content = include(controller:"book", action:"list")
最后的内容有 include标签的返回值提供 .
6.2.5 Sitemesh内容块
虽然,这对于装饰全部页面非常有用,有时,你需要装饰站点的部分独自的页面。为了实现这个可以使用内容块. 在开始时,你需要使用 <content>
标签分隔装饰页面 :
<content tag="navbar"> … draw the navbar here… </content> <content tag="header"> … draw the header here… </content> <content tag="footer"> … draw the footer here… </content> <content tag="body"> … draw the body here… </content>
随后,在布局内部,你可以引用这些组件并为每个引用单个布局:
<html> <body> <div id="header"> <g:applyLayout name="headerLayout"><g:pageProperty name="page.header"></g:applyLayout> </div> <div id="nav"> <g:applyLayout name="navLayout"><g:pageProperty name="page.navbar"></g:applyLayout> </div> <div id="body"> <g:applyLayout name="bodyLayout"><g:pageProperty name="page.body"></g:applyLayout> </div> <div id="footer"> <g:applyLayout name="footerLayout"><g:pageProperty name="page.footer"></g:applyLayout> </div> </body> </html>