(Quick Reference)

6.3 标签库 - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: null

6.3 标签库

Like Java Server Pages (JSP), GSP supports the concept of custom tag libraries. Unlike JSP, Grails' tag library mechanism is simple, elegant and completely reloadable at runtime.

Quite simply, to create a tag library create a Groovy class that ends with the convention TagLib and place it within the grails-app/taglib directory:

class SimpleTagLib {

}

Now to create a tag create a Closure property that takes two arguments: the tag attributes and the body content:

class SimpleTagLib {
    def simple = { attrs, body ->

} }

The attrs argument is a Map of the attributes of the tag, whilst the body argument is a Closure that returns the body content when invoked:

class SimpleTagLib {
    def emoticon = { attrs, body ->
       out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
    }
}

As demonstrated above there is an implicit out variable that refers to the output Writer which you can use to append content to the response. Then you can reference the tag inside your GSP; no imports are necessary:

<g:emoticon happy="true">Hi John</g:emoticon>

To help IDEs like SpringSource Tool Suite (STS) and others autocomplete tag attributes, you should add Javadoc comments to your tag closures with @attr descriptions. Since taglibs use Groovy code it can be difficult to reliably detect all usable attributes.

For example:

class SimpleTagLib {

/** * Renders the body with an emoticon. * * @attr happy whether to show a happy emoticon ('true') or * a sad emoticon ('false') */ def emoticon = { attrs, body -> out << body() << (attrs.happy == 'true' ? " :-)" : " :-(") } }

and any mandatory attributes should include the REQUIRED keyword, e.g.

class SimpleTagLib {

/** * Creates a new password field. * * @attr name REQUIRED the field name * @attr value the field value */ def passwordField = { attrs -> attrs.type = "password" attrs.tagName = "passwordField" fieldImpl(out, attrs) } }

Java Server Pages (JSP)类似,GSP支持自定义标签库的概念。而跟JSP不同的是,Grails的标签库机制是简单而优雅的,并且完全可以在运行时重新加载。

要创建一个标签库是很简单的,只需要根据规约创建一个以TagLib结尾的Groovy类,并且放到grails-app/taglib下边就好了:

class SimpleTagLib {

}

现在,要创建一个标签,只需要创建一个有两个参数(标签属性和主体内容)的闭包属性:

class SimpleTagLib {
    def simple = { attrs, body ->

} }

attrs参数是此标签的属性,类型为映射(Map),而body参数是一个闭包,它在被调用的时候将返回一个主体内容:

class SimpleTagLib {
    def emoticon = { attrs, body ->
       out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
    }
}

如上述示例所示,隐式的out变量将引用Writer输出器,用以往响应中追加内容。因此,你可以在不导入任何东西的情况下,于你的GSP内使用标签:

<g:emoticon happy="true">Hi John</g:emoticon>

为了有助于像SpringSource Tool Suite (STS)这样的IDE来自动补齐标签属性,你应该在Javadoc注释中增加标签闭包的@attr描述。因为标签库也是Groovy代码,因此不能保证检测到的所有属性都是准确可靠的。

比如:

class SimpleTagLib {

/** * Renders the body with an emoticon. * * @attr happy whether to show a happy emoticon ('true') or * a sad emoticon ('false') */ def emoticon = { attrs, body -> out << body() << (attrs.happy == 'true' ? " :-)" : " :-(") } }

并且,任何必须的属性都应该包含REQUIRED关键字,比如:

class SimpleTagLib {

/** * Creates a new password field. * * @attr name REQUIRED the field name * @attr value the field value */ def passwordField = { attrs -> attrs.type = "password" attrs.tagName = "passwordField" fieldImpl(out, attrs) } }

6.3.1 变量和作用域

Within the scope of a tag library there are a number of pre-defined variables including:
  • actionName - The currently executing action name
  • controllerName - The currently executing controller name
  • flash - The flash object
  • grailsApplication - The GrailsApplication instance
  • out - The response writer for writing to the output stream
  • pageScope - A reference to the pageScope object used for GSP rendering (i.e. the binding)
  • params - The params object for retrieving request parameters
  • pluginContextPath - The context path to the plugin that contains the tag library
  • request - The HttpServletRequest instance
  • response - The HttpServletResponse instance
  • servletContext - The javax.servlet.ServletContext instance
  • session - The HttpSession instance

在一个标签库的作用域内,已经预定义了一些变量,它们包括:

6.3.2 简单标签

As demonstrated it the previous example it is easy to write simple tags that have no body and just output content. Another example is a dateFormat style tag:

def dateFormat = { attrs, body ->
    out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}

The above uses Java's SimpleDateFormat class to format a date and then write it to the response. The tag can then be used within a GSP as follows:

<g:dateFormat format="dd-MM-yyyy" date="${new Date()}" />

With simple tags sometimes you need to write HTML mark-up to the response. One approach would be to embed the content directly:

def formatBook = { attrs, body ->
    out << "<div id="${attrs.book.id}">"
    out << "Title : ${attrs.book.title}"
    out << "</div>"
}

Although this approach may be tempting it is not very clean. A better approach would be to reuse the render tag:

def formatBook = { attrs, body ->
    out << render(template: "bookTemplate", model: [book: attrs.book])
}

And then have a separate GSP template that does the actual rendering.

正如以前示例所演示的那样,要写一个只输出内容而没有主体(body)的标签是很容易的。另外的一个示例是dateFormat风格的标签:

def dateFormat = { attrs, body ->
    out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}

在上例中,使用了Java的SimpleDateFormat类来格式化一个日期,并且将它写回到响应中。然后标签就可以在GSP中像下面所示那样使用:

<g:dateFormat format="dd-MM-yyyy" date="${new Date()}" />

在简单标签中,有时候需要你将HTML标记内容写回响应。一种方法是直接将内容内嵌到标签中:

def formatBook = { attrs, body ->
    out << "<div id="${attrs.book.id}">"
    out << "Title : ${attrs.book.title}"
    out << "</div>"
}

虽然此种方式很直接诱人,但是很不简洁。更好的一种方式是复用render标签:

def formatBook = { attrs, body ->
    out << render(template: "bookTemplate", model: [book: attrs.book])
}

这样就分离出一个GSP模板来处理真正的渲染。

6.3.3 逻辑标签

You can also create logical tags where the body of the tag is only output once a set of conditions have been met. An example of this may be a set of security tags:

def isAdmin = { attrs, body ->
    def user = attrs.user
    if (user && checkUserPrivs(user)) {
        out << body()
    }
}

The tag above checks if the user is an administrator and only outputs the body content if he/she has the correct set of access privileges:

<g:isAdmin user="${myUser}">
    // some restricted content
</g:isAdmin>

你也可以创建一个逻辑标签,一旦一组条件表达式满足,就输出标签的主体。一组安全标签的示例如下:

def isAdmin = { attrs, body ->
    def user = attrs.user
    if (user && checkUserPrivs(user)) {
        out << body()
    }
}

上述标签将检查用户是否为一个管理员,并且只有他/她有正确的访问权限的时候,才可以输出主体内容:

<g:isAdmin user="${myUser}">
    // some restricted content
</g:isAdmin>

6.3.4 迭代标签

Iterative tags are easy too, since you can invoke the body multiple times:

def repeat = { attrs, body ->
    attrs.times?.toInteger()?.times { num ->
        out << body(num)
    }
}

In this example we check for a times attribute and if it exists convert it to a number, then use Groovy's times method to iterate the specified number of times:

<g:repeat times="3">
<p>Repeat this 3 times! Current repeat = ${it}</p>
</g:repeat>

Notice how in this example we use the implicit it variable to refer to the current number. This works because when we invoked the body we passed in the current value inside the iteration:

out << body(num)

That value is then passed as the default variable it to the tag. However, if you have nested tags this can lead to conflicts, so you should should instead name the variables that the body uses:

def repeat = { attrs, body ->
    def var = attrs.var ?: "num"
    attrs.times?.toInteger()?.times { num ->
        out << body((var):num)
    }
}

Here we check if there is a var attribute and if there is use that as the name to pass into the body invocation on this line:

out << body((var):num)

Note the usage of the parenthesis around the variable name. If you omit these Groovy assumes you are using a String key and not referring to the variable itself.

Now we can change the usage of the tag as follows:

<g:repeat times="3" var="j">
<p>Repeat this 3 times! Current repeat = ${j}</p>
</g:repeat>

Notice how we use the var attribute to define the name of the variable j and then we are able to reference that variable within the body of the tag.

因为你可以多次调用主体(body),所以迭代标签也是很容易的:

def repeat = { attrs, body ->
    attrs.times?.toInteger()?.times { num ->
        out << body(num)
    }
}

在此例中,我们检查标签的times属性,如果存在呢,就将其转换为一个数字,然后使用Groovy的times方法来迭代给定的次数:

<g:repeat times="3">
<p>Repeat this 3 times! Current repeat = ${it}</p>
</g:repeat>

注意示例中,我们是如何使用隐式的it变量来引用当前的数字。此种方式是有效的,因为在迭代内部,我们将当前值传给了正在调用的主体(body):

out << body(num)

然后那个值作为缺省it变量传给了标签。但是,如果你有嵌套的标签的话,那么这将会导致冲突,因此你应该给给调用的主体变量命名:

def repeat = { attrs, body ->
    def var = attrs.var ?: "num"
    attrs.times?.toInteger()?.times { num ->
        out << body((var):num)
    }
}

此处我们检查是否存在一个var属性,如果有,那么将使用其值作为变量名称传递给正在调用的主体,如下所示:

out << body((var):num)

注意!变量名称两边的括号。如果你忽略它们,那么Groovy将会认为你正在使用一个String类型的键,而不是变量本身。

现在你可以修改标签的使用方法了,如下所示:

<g:repeat times="3" var="j">
<p>Repeat this 3 times! Current repeat = ${j}</p>
</g:repeat>

请注意我们是如何使用var属性来将变量名称定义为j,然后就可以在标签的主体内来引用此变量了。 tag.

6.3.5 标签命名空间

By default, tags are added to the default Grails namespace and are used with the g: prefix in GSP pages. However, you can specify a different namespace by adding a static property to your TagLib class:

class SimpleTagLib {
    static namespace = "my"

def example = { attrs -> … } }

Here we have specified a namespace of my and hence the tags in this tag lib must then be referenced from GSP pages like this:

<my:example name="..." />

where the prefix is the same as the value of the static namespace property. Namespaces are particularly useful for plugins.

Tags within namespaces can be invoked as methods using the namespace as a prefix to the method call:

out << my.example(name:"foo")

This works from GSP, controllers or tag libraries

一般情况下,标签使用Grails的缺省命名空间,并且在GSP页面中使用g:前缀。但是你也可以通过在TagLib类中增加一个静态属性来指定另外一个命名空间:

class SimpleTagLib {
    static namespace = "my"

def example = { attrs -> … } }

此处我们将namespace指定为my,因此此标签库的标签在GSP页面中必须像如下所示那样引用:

<my:example name="..." />

前缀部分跟静态属性namespace的值是一样的。命名空间对插件来说特别有用。

带命名空间的标签也可以以方法的方式调用,需要将其命名空间作为前缀赋给方法调用:

out << my.example(name:"foo")

这将在GSP、控制器或者标签库中有效

6.3.6 使用JSP标签库

In addition to the simplified tag library mechanism provided by GSP, you can also use JSP tags from GSP. To do so simply declare the JSP to use with the taglib directive:

<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>

Then you can use it like any other tag:

<fmt:formatNumber value="${10}" pattern=".00"/>

With the added bonus that you can invoke JSP tags like methods:

${fmt.formatNumber(value:10, pattern:".00")}

6.3.7 标签的返回值

Since Grails 1.2, a tag library call returns an instance of org.codehaus.groovy.grails.web.util.StreamCharBuffer class by default. This change improves performance by reducing object creation and optimizing buffering during request processing. In earlier Grails versions, a java.lang.String instance was returned.

Tag libraries can also return direct object values to the caller since Grails 1.2.. Object returning tag names are listed in a static returnObjectForTags property in the tag library class.

Example:

class ObjectReturningTagLib {
    static namespace = "cms"
    static returnObjectForTags = ['content']

def content = { attrs, body -> CmsContent.findByCode(attrs.code)?.content } }