タグライブラリの変態っぷりに茶ぁ吹いた(ほめてる)

feedsプラグインを使ってて発見したのだが,まずはこのコントローラのコードを見て欲しい。

def feeds = {
  render(feedType:"rss", feedVersion:"2.0") {
    title = "My test feed"
    link = g.createLink(controller:"blog", absolute: true)
    Blog.list().each() { blog ->
      entry(blog.title) {
        link = g.createLink(controller:"blog", action:"show", id:blog.id, absolute:true)
      }
    }
  }
}

注目は「g.createLink()」。なんぞこれ?と思ってみたが,答えは簡単,GSPのcreateLinkタグだった。
どうやら,GSPのタグライブラリはコントローラからも使えるらしい。そりゃ,どんな裏技だと思いリファレンスひいてみたら,それっぽい記述を発見。

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

6.3.5 Tag Namespaces - Grails Framework Reference Document


要するにタグライブラリはGSPやコントローラ内でメソッドとしても利用できるそうだ。JSPのtaglibがFunctionsも兼用しているって感じなんだろう。だから,下記の2つは同じ意味になる。
#標準タグの場合,GSP中ではネームスペース 'g' は省略できるみたい。

<g:createLink action="show" id="1"/>
${createLink(action:"show', id:"1")}


試しにコントローラ内で「println g.class.name」とかってやると,こんなのが返ってきた(独自タグを作った場合は,gをそのタグのネームスペースに置き換えると良い)。

org.codehaus.groovy.grails.web.taglib.NamespacedTagDispatcher


だから,コントローラからもユーティリティ的にタグライブラリを利用することができるわけだ。リファレンスに書いてあるくらいだから,当たり前に使って良いんだよね。つか,これ便利すぎるよ,ママン!!:-)


ちょうどJSON形式で複数をHTML片を返したいなって思ったんだが,コントローラのrender(template:〜)じゃ実現できないのに気付いたワケ。だって,これ戻り値無いでしょ。かといって,MarkupBuilderでHTML片作るのもたるくてJSONで返すの諦めてたんだけど,この方法知って,あっという間に解決した。
つまり,こんな風に書けば,複数のテンプレートを束ねることができるのだ。

render([main: g.render(template: "main_tempalte",
                       model: [foo: foo, bar: bar]),
        sub: g.render(template: "sub_template",
                      model: [hoge:hoge])] as JSON)

↑ちょっとわかりづらいけど,renderタグをメソッドとして利用している(g.render(〜んとこ)。むろんテンプレの中にはGSPタグを使ってたって構わない。この超便利さ,わかってくれるかしら?


ちなみにサービスではこの方法は使えないので,このリンク先にあるようにGroovyPagesTemplateEngineを使うらしい。試してないからアレだけど,例えば電子メールのテンプレにGSPを使いたい場合などに使えるようだ。
Grails – Rendering a Template from a String | Disgruntled Developers


あと,AcegiSecurityプラグイン使ってんだけど,これもいくつか便利なタグがあるんのね。たとえば,ログインしてる時だけ評価されるような,g:isLoggedInタグとか。

<g:isLoggedIn>
ログインしてると,ここが表示されるス
</g:isLoggedIn>


もちっと器用に「ログインしてて,かつ自分のものだったら評価する」みたいな条件付けも,今回の件で解決した。Acegiにはログインしているユーザ名を出力するg:loggedInUsernameってタグがあるんで,これを使ってこんな感じにするとできた。

<g:isLoggedIn><g:if test="${loggedInUsername() == account.username}">
ログインしててかつ,accountが自分のモノなら,ここが表示される。
</g:if></g:isLoggedIn>


わはは。融通利きすぎて笑える。Grailsって発想が斜め上を行きすぎてて,もうJSP使えないかも知んない。:-P
あわせて読みたい。→ うぬはまだEL(式言語)の本当の恐ろしさをしらぬ - しんさんの出張所 はてなブログ編


ps.
どうやらタグライブラリをメソッドとして使った場合,out変数に突っ込んだ内容が戻り値となるようですね。あと当然,タグボディは評価されない。
LogicalタグやIterativeタグがどう評価されるかは試してないけど,無理に試すもんでもないか(必要になったら確認してみる)。

      • -

2009.06.20 追記:
ついてる時はどこまでもついているのぅ。
groovy - Grails link taglib use outside of GSP - Stack Overflow


タグライブラリをメソッドとして評価した場合,タグボディはキーワード無しパラメタかクロージャで渡せば良いんだそうな。なので,

<g:link controller="foo" action="foo">text of the link</g:link>

は,こんな風に書ける。

g.link(controller:"foo", action:"bar", "text of the link")
g.link(controller:"foo", action:"bar") { "text of the link" }


タグライブラリのシグネチャ

def tagname = { attrs, body -> ... }


なので,なるほどと言えばなるほどな答え。