Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ Adding the `TagLibUnitTest` trait to a test causes a new `tagLib` field to be
automatically created for the TagLib class under test. The `tagLib` property can
be used to test calling tags as function calls. The return value of a function
call is either a `org.grails.buffer,StreamCharBuffer`
instance or the object returned from the tag closure when
instance or the object returned from the tag handler when
`returnObjectForTags` feature is used.

To test a tag which accepts parameters, specify the parameter values as named
Expand Down
49 changes: 42 additions & 7 deletions grails-doc/src/en/guide/theWebLayer/gsp/taglibs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,71 @@ class SimpleTagLib {
}
----

Now to create a tag create a Closure property that takes two arguments: the tag attributes and the body content:
Now create tags using methods. You can access tag attributes through the implicit `attrs` map and body through the implicit `body` closure:

[source,groovy]
----
class SimpleTagLib {
def simple = { attrs, body ->
def simple() {
// ...

}
}
----

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:
Closure field-style tags are still supported for backward compatibility, but method-based tags are the recommended syntax.

The implicit `attrs` property is a `Map` of the tag attributes, while `body()` returns the tag body content when invoked:

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

For method-based tags, named attributes can also bind directly to method signature arguments:

[source,groovy]
----
class SimpleTagLib {
def greeting(String name) {
out << "Hello, ${name}!"
}
}
----

Used as:

[source,xml]
----
<g:greeting name="Graeme" />
----

For tags with strict validation/error handling, you can keep a `Map attrs` handler and add typed overloads that delegate to it:

[source,groovy]
----
def field(Map attrs) {
// existing validation + rendering path
}

def field(String type, Map attrs) {
attrs.type = type
field(attrs)
}
----

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:

[source,xml]
----
<g:emoticon happy="true">Hi John</g:emoticon>
----

NOTE: To help IDEs like Spring 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.
NOTE: To help IDEs like Spring Tool Suite (STS) and others autocomplete tag attributes, add Javadoc comments with `@attr` descriptions to your tag methods. Since taglibs use Groovy code it can be difficult to reliably detect all usable attributes.

For example:

Expand All @@ -71,7 +106,7 @@ class SimpleTagLib {
* @attr happy whether to show a happy emoticon ('true') or
* a sad emoticon ('false')
*/
def emoticon = { attrs, body ->
def emoticon() {
out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
}
}
Expand All @@ -89,7 +124,7 @@ class SimpleTagLib {
* @attr name REQUIRED the field name
* @attr value the field value
*/
def passwordField = { attrs ->
def passwordField() {
attrs.type = "password"
attrs.tagName = "passwordField"
fieldImpl(out, attrs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ Iterative tags are easy too, since you can invoke the body multiple times:

[source,groovy]
----
def repeat = { attrs, body ->
def repeat() {
attrs.times?.toInteger()?.times { num ->
out << body(num)
}
Expand All @@ -48,7 +48,7 @@ That value is then passed as the default variable `it` to the tag. However, if y

[source,groovy]
----
def repeat = { attrs, body ->
def repeat() {
def var = attrs.var ?: "num"
attrs.times?.toInteger()?.times { num ->
out << body((var):num)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ You can also create logical tags where the body of the tag is only output once a

[source,groovy]
----
def isAdmin = { attrs, body ->
def isAdmin() {
def user = attrs.user
if (user && checkUserPrivs(user)) {
out << body()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ By default, tags are added to the default Grails namespace and are used with the
----
class SimpleTagLib {
static namespace = "my"

def example() {
def example = { attrs ->
//...
}
Expand Down
15 changes: 12 additions & 3 deletions grails-doc/src/en/guide/theWebLayer/gsp/taglibs/simpleTags.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ As demonstrated in the previous example it is easy to write simple tags that hav

[source,groovy]
----
def dateFormat = { attrs, body ->
def dateFormat() {
out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}
----
Expand All @@ -33,11 +33,20 @@ The above uses Java's `SimpleDateFormat` class to format a date and then write i
<g:dateFormat format="dd-MM-yyyy" date="${new Date()}" />
----

With method-based tags, attributes may also bind directly to method parameters by name:

[source,groovy]
----
def dateFormat(String format, Date date) {
out << new java.text.SimpleDateFormat(format).format(date)
}
----

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

[source,groovy]
----
def formatBook = { attrs, body ->
def formatBook() {
out << "<div id=\"${attrs.book.id}\">"
out << "Title : ${attrs.book.title}"
out << "</div>"
Expand All @@ -48,7 +57,7 @@ Although this approach may be tempting it is not very clean. A better approach w

[source,groovy]
----
def formatBook = { attrs, body ->
def formatBook() {
out << render(template: "bookTemplate", model: [book: attrs.book])
}
----
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,13 @@ under the License.

A taglib can be used in a GSP as an ordinary tag, or it might be used as a function in other taglibs or GSP expressions.

Internally Grails intercepts calls to taglib closures.
Internally Grails intercepts calls to tag handlers (method-based or closure-based).
The "out" that is available in a taglib is mapped to a `java.io.Writer` implementation that writes to a buffer
that "captures" the output of the taglib call. This buffer is the return value of a tag library call when it's
used as a function.

If the tag is listed in the library's static `returnObjectForTags` array, then its return value will be written to
the output when it's used as a normal tag. The return value of the tag lib closure will be returned as-is
the output when it's used as a normal tag. The return value of the tag method/closure will be returned as-is
if it's used as a function in GSP expressions or other taglibs.

If the tag is not included in the returnObjectForTags array, then its return value will be discarded.
Expand All @@ -37,7 +37,7 @@ Example:
class ObjectReturningTagLib {
static namespace = "cms"
static returnObjectForTags = ['content']

def content() {
def content = { attrs, body ->
CmsContent.findByCode(attrs.code)?.content
}
Expand Down
49 changes: 42 additions & 7 deletions grails-doc/src/en/guide/theWebLayer/taglibs.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -28,36 +28,71 @@ class SimpleTagLib {
}
----

Now to create a tag create a Closure property that takes two arguments: the tag attributes and the body content:
Now create tags using methods. You can access tag attributes through the implicit `attrs` map and body through the implicit `body` closure:

[source,groovy]
----
class SimpleTagLib {
def simple = { attrs, body ->
def simple() {
// ...

}
}
----

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:
Closure field-style tags are still supported for backward compatibility, but method-based tags are the recommended syntax.

The implicit `attrs` property is a `Map` of the tag attributes, while `body()` returns the tag body content when invoked:

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

For method-based tags, named attributes can also bind directly to method signature arguments:

[source,groovy]
----
class SimpleTagLib {
def greeting(String name) {
out << "Hello, ${name}!"
}
}
----

Used as:

[source,xml]
----
<g:greeting name="Graeme" />
----

For tags with strict validation/error handling, keep a `Map attrs` handler and add typed overloads that delegate to it:

[source,groovy]
----
def field(Map attrs) {
// existing validation + rendering path
}

def field(String type, Map attrs) {
attrs.type = type
field(attrs)
}
----

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:

[source,xml]
----
<g:emoticon happy="true">Hi John</g:emoticon>
----

NOTE: To help IDEs 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.
NOTE: To help IDEs autocomplete tag attributes, add Javadoc comments with `@attr` descriptions to your tag methods. Since taglibs use Groovy code it can be difficult to reliably detect all usable attributes.

For example:

Expand All @@ -71,7 +106,7 @@ class SimpleTagLib {
* @attr happy whether to show a happy emoticon ('true') or
* a sad emoticon ('false')
*/
def emoticon = { attrs, body ->
def emoticon() {
out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
}
}
Expand All @@ -89,7 +124,7 @@ class SimpleTagLib {
* @attr name REQUIRED the field name
* @attr value the field value
*/
def passwordField = { attrs ->
def passwordField() {
attrs.type = "password"
attrs.tagName = "passwordField"
fieldImpl(out, attrs)
Expand Down
Loading
Loading