7 Webレイヤ - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: 2.3.0
Translated by: T.Yamamoto, Japanese Grails Doc Translating Team. Special thanks to NTT Software.
【注意】このドキュメントの内容はスナップショットバージョンを元に*意訳*されているため、一部現行バージョンでは未対応の機能もあります。
Table of Contents
7 Webレイヤ
7.1 コントローラ
Controller
in the grails-app/controllers
directory (in a subdirectory if it's in a package).grails-app/controlers
ディレクトリに、クラス名称の最後をController
にしたクラスを作成することで、コントローラを追加できます。(パッケージに入れる場合は、パッケージ名のサブディレクトリに作成します。)7.1.1 コントローラとアクションの理解
コントローラを作成する
grails create-controller book
grails-app/controllers/myapp/BookController.groovy
:grails-app/controllers/myapp/BookController.groovy
にコントローラを生成します。package myappclass BookController {
def index() { } }
BookController
by default maps to the /book URI (relative to your application root).BookController
は、URIが /bookとなります。(アプリケーションのコンテキストルートからの相対パス)Thecreate-controller
andgenerate-controller
commands are just for convenience and you can just as easily create controllers using your favorite text editor or IDEcreate-controller
とgenerate-controller
コマンドは、簡単にコントローラを生成する便利なコマンドですが、IDEやテキストエディタの機能でクラスを作成してもかまいません。
create-controller
とgenerate-controller
コマンドは、簡単にコントローラを生成する便利なコマンドですが、IDEやテキストエディタの機能でクラスを作成してもかまいません。
アクションの作成
class BookController {def list() {
// do controller logic // create model
return model } }
/book/list
URI by default thanks to the property being named list
.list
なので、URIは/book/list
にマップされます。パブリックメソッドをアクションとする
- Memory efficient
- Allow use of stateless controllers (
singleton
scope) - You can override actions from subclasses and call the overridden superclass method with
super.actionName()
- Methods can be intercepted with standard proxying mechanisms, something that is complicated to do with Closures since they're fields.
- メモリ効率
- ステートレスコントローラの使用 (
シングルトンスコープ
指定) - サブクラスでのアクションのオーバーライドが可能。スーパークラスのアクションメソッド呼び出しが可能。
super.actionName()
- クロージャはフィールドのため複雑だった、スタンダードなプロキシを使用したメソッドのインターセプトが可能。
grails.compile.artefacts.closures.convert
property to true in BuildConfig.groovy
:BuildConfig.groovy
に、 grails.compile.artefacts.closures.convert
を定義します:
grails.compile.artefacts.closures.convert = true
If a controller class extends some other class which is not defined under the grails-app/controllers/
directory, methods inherited from that class are not converted to controller actions. If the intent is to expose those inherited methods as controller actions the methods may be overridden in the subclass and the subclass method may invoke the method in the super class.
デフォルトアクション
/book
for BookController
. The action that is called when the default URI is requested is dictated by the following rules:BookController
の場合は /book
になります。デフォルトURIが呼ばれたときに呼ばれるアクションは以下のルールになっています:
- If there is only one action, it's the default
- If you have an action named
index
, it's the default - Alternatively you can set it explicitly with the
defaultAction
property:
- アクションが1つの場合は、それがデフォルトになります。
index
という名称のアクションがある場合は、それがデフォルトになります。defaultAction
を定義して指定できます。
static defaultAction = "list"
7.1.2 コントローラとスコープ
使用可能なスコープ
- servletContext - Also known as application scope, this scope lets you share state across the entire web application. The servletContext is an instance of ServletContext
- session - The session allows associating state with a given user and typically uses cookies to associate a session with a client. The session object is an instance of HttpSession
- request - The request object allows the storage of objects for the current request only. The request object is an instance of HttpServletRequest
- params - Mutable map of incoming request query string or POST parameters
- flash - See below
- servletContext - webアプリケーションの全体にわたってスコープを共有するアプリケーションスコープです。ServletContextのインスタンスです。
- session - セッションは、通常クッキーを使用してリクエストを行ったクライアントとの連携をして、ステートを保持します。HttpSessionのインスタンスです。
- request - リクエストオブジェクトはリクエスト時のみオブジェクトを保持するオブジェクトです。HttpServletRequestのインスタンスです。
- params - 受け取ったリクエストクエリー文字列または、POSTパラメータを保持するミュータブルマップです。
- flash - 以下を参照
スコープへのアクセス
class BookController { def find() { def findBy = params["findBy"] def appContext = request["foo"] def loggedUser = session["logged_user"] } }
class BookController { def find() { def findBy = params.findBy def appContext = request.foo def loggedUser = session.logged_user } }
フラッシュスコープを使用する
def delete() { def b = Book.get(params.id) if (!b) { flash.message = "User not found for id ${params.id}" redirect(action:list) } … // remaining code }
list
action is requested, the message
value will be in scope and can be used to display an information message. It will be removed from the flash
scope after this second request.delete
アクションに対してリクエストをすると、リダイレクトされてlist
アクションが実行された際に、flashスコープのmessage
に代入した内容がメッセージ表示等に使用可能になります。このリダイレクトされたlist
アクション(delete
の次のアクション)の処理が終了したら、flash
スコープの内容が削除されます。コントローラのスコープ
prototype
(default) - A new controller will be created for each request (recommended for actions as Closure properties)session
- One controller is created for the scope of a user sessionsingleton
- Only one instance of the controller ever exists (recommended for actions as methods)
prototype
(デフォルト) - リクエスト毎にコントローラが生成されます。(クロージャでアクションを推奨)session
- ユーザのセッションを通して1個のコントローラが生成されます。singleton
- 1個のコントローラのみが存在する状態です。(メソッドでのアクションをお勧めします)
scope
property to your class with one of the valid scope values listed above, for examplestatic scope = "singleton"
Config.groovy
with the grails.controllers.defaultScope
key, for example:Config.groovy
にgrails.controllers.defaultScope
を指定する事で変更できます。grails.controllers.defaultScope = "singleton"
Newly created applications have the grails.controllers.defaultScope
property set in grails-app/conf/Config.groovy
with a value of "singleton". You may change this value to any
of the supported scopes listed above. If the property is not assigned a value at all, controllers will default to "prototype" scope.
Use scoped controllers wisely. For instance, we don't recommend having any properties in a singleton-scoped controller since they will be shared for all requests. Setting a default scope other thanprototype
may also lead to unexpected behaviors if you have controllers provided by installed plugins that expect that the scope isprototype
.
コントローラのスコープは使用状況を考えてお使いください。例えば、シングルトンのコントローラは、_全ての_リクエストで共有されるためプロパティを持つべきではありません。デフォルトで定義されているスコープのprototype
を変更することで、プラグインなどで提供されたコントローラがprotopype
を前提で実装されている場合があるので注意しましょう
7.1.3 モデルとビュー
モデルを返す
def show() { [book: Book.get(params.id)] }
The above does not reflect what you should use with the scaffolding views - see the scaffolding section for more details.上記の例はスカッフォルドのビューで使用する例ではありません。スカッフォルドに関しては、スカッフォルドのセクションを参照してください。
class BookController {List books List authors
def list() { books = Book.list() authors = Author.list() } }
This is possible due to the fact that controllers are prototype scoped. In other words a new controller is created for each request. Otherwise code such as the above would not be thread-safe, and all users would share the same data.これはコントローラのスコープがprototype(リクエスト毎にコントローラが生成される)の場合に可能な方法です。他の場合はスレッドセーフではありません。
books
and authors
properties will be available in the view.book
とauthors
がビューで参照できます。import org.springframework.web.servlet.ModelAndViewdef index() { // get some books just for the index page, perhaps your favorites def favoriteBooks = ...
// forward to the list view to show them return new ModelAndView("/book/list", [ bookList : favoriteBooks ]) }
attributes
application
ビューの選択Selecting the View
grails-app/views/book/show.gsp
for this show
action:grails-app/views/book/show.gsp
をビューとして使用します。class BookController { def show() { [book: Book.get(params.id)] } }
def show() {
def map = [book: Book.get(params.id)]
render(view: "display", model: map)
}
grails-app/views/book/display.gsp
. Notice that Grails automatically qualifies the view location with the book
directory of the grails-app/views
directory. This is convenient, but to access shared views you need instead you can use an absolute path instead of a relative one:grails-app/views/book/display.gsp
の描写を試みます。注目する場所はGrailsは自動的にコントローラがbook
であれば、grails-app/views
ディレクトリのbook
ディレクトリを、指定しなくても取得する所です。これは便利なのですが、ビューを共有する場合は、パスを指定する必要があります。def show() {
def map = [book: Book.get(params.id)]
render(view: "/shared/display", model: map)
}
grails-app/views/shared/display.gsp
.grails-app/views/shared/display.gsp
の描写を試みます。レスポンスの描写
render
method can be used:render
メソッドを使うと簡単にできます。render "Hello World!"
// マークアップを返す
render {
for (b in books) {
div(id: b.id, b.title)
}
}
// 指定したビューを描写 render(view: 'show')
// コレクションの中身をそれぞれ指定したテンプレートを適応して描写 render(template: 'book_template', collection: Book.list())
// 文字列を指定したエンコーディングとコンテントタイプで描写 render(text: "<xml>some xml</xml>", contentType: "text/xml", encoding: "UTF-8")
MarkupBuilder
to generate HTML for use with the render
method be careful of naming clashes between HTML elements and Grails tags, for example:render
メソッドでGroovyのMarkupBuilder
を使用してHTMLを生成する場合は、HTMLエレメント名とGrailsタグとの名称衝突を気をつけましょう。(Grailsのタグはアクション内部でメソッドとして使用できるため)import groovy.xml.MarkupBuilder … def login() { def writer = new StringWriter() def builder = new MarkupBuilder(writer) builder.html { head { title 'Log in' } body { h1 'Hello' form { } } }def html = writer.toString() render html }
MarkupBuilder
). To correctly output a <form>
element, use the following:<form>
タグを正常に生成するには、以下のようにbuilder.form
とします。def login() { // … body { h1 'Hello' builder.form { } } // … }
7.1.4 リダイレクトとチェイン
リダイレクトRedirects
class OverviewController {def login() {}
def find() { if (!session.user) redirect(action: 'login') return } … } }
sendRedirect
メソッドを使用しています。redirect
method expects one of:redirect
メソッドでは以下のいずれかのように使用します:// Call the login action within the same class redirect(action: login)
- アクション名称でのしていと、別のコントローラのアクションの場合はコントローラの指定:
// Also redirects to the index action in the home controller redirect(controller: 'home', action: 'index')
- アプリケーションコンテキストパス内のリソースのURI:
// Redirect to an explicit URI
redirect(uri: "/login.html")
- URLを全指定:
// Redirect to a URL
redirect(url: "http://grails.org")
params
argument of the method:redirect(action: 'myaction', params: [myparam: "myvalue"])
params
object is a Map, you can use it to pass the current request parameters from one action to the next:params
オブジェクトはMapなので、以下のように、そのまま全てのリクエストパラメータを次のアクションに渡すことができます:redirect(action: "next", params: params)
redirect(controller: "test", action: "show", fragment: "profile")
チェイニング Chaining
first
action in this action:first
を呼び出します:class ExampleChainController {def first() { chain(action: second, model: [one: 1]) }
def second () { chain(action: third, model: [two: 2]) }
def third() { [three: 3]) } }
[one: 1, two: 2, three: 3]
chainModel
map. This dynamic property only exists in actions following the call to the chain
method:class ChainController {def nextInChain() { def model = chainModel.myModel … } }
redirect
method you can also pass parameters to the chain
method:redirect
メソッドと同じように、chain
メソッドにパラメータを渡すこともできます:chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
7.1.5 コントローラ・インターセプター
If your interceptor is likely to apply to more than one controller, you are almost certainly better off writing a Filter. Filters can be applied to multiple controllers or URIs without the need to change the logic of each controller複数のコントローラにインターセプターを反映させたい場合は、Filterを使用することをお勧めします。Filterは多数のコントローラまたはURIに、ロジックを追加すること無く反映できます。
ビフォア・インターセプションBefore Interception
beforeInterceptor
intercepts processing before the action is executed. If it returns false
then the intercepted action will not be executed. The interceptor can be defined for all actions in a controller as follows:beforeInterceptor
は、アクションが実行される前に処理の追加を行うことができます。beforeInterceptor
で、false
を返すとアクションは実行されません。コントローラ内の全てのアクションにインターセプターを定義するには以下のようにします。def beforeInterceptor = {
println "Tracing action ${actionUri}"
}
def beforeInterceptor = [action: this.&auth, except: 'login']// defined with private scope, so it's not considered an action private auth() { if (!session.user) { redirect(action: 'login') return false } }
def login() { // display login page }
auth
. A private method is used so that it is not exposed as an action to the outside world. The beforeInterceptor
then defines an interceptor that is used on all actions except the login action and it executes the auth
method. The auth
method is referenced using Groovy's method pointer syntax. Within the method it detects whether there is a user in the session, and if not it redirects to the login
action and returns false
, causing the intercepted action to not be processed.auth
メソッドを定義します。外部にアクションとして認識されないようにprivate
メソッドにします。
今回は、アクション名がlogin
以外の全てのアクションで実行されるように、実行するメソッドをaction:に指定し、除外するアクション'login'を except:に指定して、beforeInterceptor
を定義します。auth
メソッドはGroovyのメソッドポインターシンタックスで指定します。これで、auth
の処理によって、sessionにuser変数が見つからなかった場合は'login'アクションへリダイレクトしてfalse
を返しアクションを実行しないインターセプターが行えます。
アフター・インターセプション After Interception
afterInterceptor
property to define an interceptor that is executed after an action:afterInterceptor
は、アクションが実行される後に処理の追加を行うことができます:def afterInterceptor = { model ->
println "Tracing action ${actionUri}"
}
def afterInterceptor = { model, modelAndView -> println "Current view is ${modelAndView.viewName}" if (model.someVar) modelAndView.viewName = "/mycontroller/someotherview" println "View is now ${modelAndView.viewName}" }
modelAndView
may be null
if the action being intercepted called redirect
or render
.redirect
やrender
を呼び出している場合は、modelANdView
はnull
になります。インターセプションの条件指定 Interception Conditions
def beforeInterceptor = [action: this.&auth, except: 'login']
def beforeInterceptor = [action: this.&auth, except: ['login', 'register']]
def beforeInterceptor = [action: this.&auth, only: ['secure']]
7.1.6 データバインディング
マップを用いたバインディング
// grails-app/domain/Person.groovy class Person { String firstName String lastName Integer age }
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]def person = new Person(bindingMap)
assert person.firstName == 'Peter' assert person.lastNaem == 'Gabriel' assert person.age == 63
properties
property of the domain class:properties
プロパティにマップを代入できます。def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63]def person = Person.get(someId) person.properties = bindingMap
assert person.firstName == 'Peter' assert person.lastNaem == 'Gabriel' assert person.age == 63
class Person { String firstName String lastName Integer age Address homeAddress }class Address { String county String country }
def bindingMap = [firstName: 'Peter', lastName: 'Gabriel', age: 63, homeAddress: [county: 'Surrey', country: 'England'] ]def person = new Person(bindingMap)
assert person.firstName == 'Peter' assert person.lastNaem == 'Gabriel' assert person.age == 63 assert person.homeAddress.county == 'Surrey' assert person.homeAddress.country == 'England'
コレクションとマップへのバインディング
List
of objects in a domain class:List
への格納の単純な例を示します。class Band { String name static hasMany = [albums: Album] List albums }class Album { String title Integer numberOfTracks }
def bindingMap = [name: 'Genesis', 'albums[0]': [title: 'Foxtrot', numberOfTracks: 6], 'albums[1]': [title: 'Nursery Cryme', numberOfTracks: 7]]def band = new Band(bindingMap)
assert band.name == 'Genesis' assert band.albums.size() == 2 assert band.albums[0].title == 'Foxtrot' assert band.albums[0].numberOfTracks == 6 assert band.albums[1].title == 'Nursery Cryme' assert band.albums[1].numberOfTracks == 7
albums
were an array instead of a List
.albums
がList
の代わりに配列だったとしても同様に動作します。Set
the structure of the Map
being bound to the Set
is the same as that of a Map
being bound to a List
but since a Set
is unordered, the indexes don't necessarily correspond to the order of elements in the Set
. In the code example above, if albums
were a Set
instead of a List
, the bindingMap
could look exactly the same but 'Foxtrot' might be the first album in the Set
or it might be the second. When updating existing elements in a Set
the Map
being assigned to the Set
must have id
elements in it which represent the element in the Set
being updated, as in the following example:Set
へとバインディングするとき、Set
にバインドされたMap
の構造はList
にバインドされたMap
と同じですが、Set
は順序を保持しないため、添字は必ずしもSet
内の要素の順序と一致しないことに注意してください。
上記のサンプルコードでalbums
がList
の代わりにSet
だったとしたら、bindingMap
は一見まったく同じに見えるかもしれませんが、「Foxtrot」はSet
内の最初のアルバムかもしれませんし、2番目であるかもしれません。
以下の例のように、Set
内の既存の要素を更新するとき、Set
に代入するMap
は更新されようとしているSet
内の要素を表すid
要素を持っていなければなりません:/* * The value of the indexes 0 and 1 in albums[0] and albums[1] are arbitrary * values that can be anything as long as they are unique within the Map. * They do not correspond to the order of elements in albums because albums * is a Set. */ def bindingMap = ['albums[0]': [id: 9, title: 'The Lamb Lies Down On Broadway'] 'albums[1]': [id: 4, title: 'Selling England By The Pound']]def band = Band.get(someBandId)
/* * This will find the Album in albums that has an id of 9 and will set its title * to 'The Lamb Lies Down On Broadway' and will find the Album in albums that has * an id of 4 and set its title to 'Selling England By The Pound'. In both * cases if the Album cannot be found in albums then the album will be retrieved * from the database by id, the Album will be added to albums and will be updated * with the values described above. If a Album with the specified id cannot be * found in the database, then a binding error will be created and associated * with the band object. More on binding errors later. */ band.properties = bindingMap
Map
the structure of the binding Map
is the same as the structore of a Map
used for binding to a List
or a Set
and the index inside of square brackets corresponds to the key in the Map
being bound to. See the following code:Map
へとバインディングするとき、バインド元のMap
の構造はList
やSet
へのバインディングに使われるMap
と同じです。
角括弧内の添字はバインド先のMap
内のキーとなります。
以下のコードを見てください:class Album { String title static hasMany = [players: Player] Map players }class Player { String name }
def bindingMap = [title: 'The Lamb Lies Down On Broadway', 'players[guitar]': [name: 'Steve Hacket'], 'players[vocals]': [name: 'Peter Gabriel'] 'players[keyboards]': [name: 'Tony Banks']]def album = new Album(bindingMap)
assert album.title == 'The Lamb Lies Down On Broadway' assert album.players.size() == 3 assert album.players.guitar == 'Steve Hacket' assert album.players.vocals == 'Peter Gabriel' assert album.players.keyboards == 'Tony Banks'
Map
, if the key specified in the binding Map
does not exist in the Map
being bound to then a new value will be created and added to the Map
with the specified key as in the following example:Map
を更新するときに、バインド元のMap
内で指定されたキーがバインド先のMap
内に存在しない場合、新しい値が生成されて指定されたキーでバインド先のMap
に追加されます:def bindingMap = [title: 'The Lamb Lies Down On Broadway', 'players[guitar]': [name: 'Steve Hacket'], 'players[vocals]': [name: 'Peter Gabriel'] 'players[keyboards]': [name: 'Tony Banks']]def album = new Album(bindingMap)
assert album.title == 'The Lamb Lies Down On Broadway' assert album.players.size() == 3 assert album.players.guitar == 'Steve Hacket' assert album.players.vocals == 'Peter Gabriel' assert album.players.keyboards == 'Tony Banks'
def updatedBindingMap = ['players[drums]': [name: 'Phil Collins'], 'players[keyboards]': [name: 'Anthony George Banks']]
album.properties = updatedBindingMap
assert album.title == 'The Lamb Lies Down On Broadway' assert album.players.size() == 4 assert album.players.guitar == 'Steve Hacket' assert album.players.vocals == 'Peter Gabriel' assert album.players.keyboards == 'Anthony George Banks' assert album.players.drums == 'Phil Collins'
モデルにリクエストデータをバインドする
person.homeAddress.country
and person.homeAddress.city
with values 'USA' and 'St. Louis' respectively, params
would include entries like these:person.homeAddress.country
とperson.homeAddress.city
という名前のリクエストパラメータを持っているリクエストの場合、params
のエントリは以下のようになります:[person: [homeAddress: [country: 'USA', city: 'St. Louis']]]
def save() {
def b = new Book(params)
b.save()
}
new Book(params)
. By passing the params object to the domain class constructor Grails automatically recognizes that you are trying to bind from request parameters. So if we had an incoming request like:new Book(params)
の部分でデータバインディングが実行されます。paramsオブジェクトをドメインクラスのコンストラクタに渡すことで、Grailsが自動にリクエストパラメータを認識してバインドします。例えば、以下のようなリクエストを受信したとします。/book/save?title=The%20Stand&author=Stephen%20King
title
and author
request parameters would automatically be set on the domain class. You can use the properties property to perform data binding onto an existing instance:title
とauthor
が、自動的にドメインクラスへセットされます。もう1つの方法として、既存のドメインクラスインスタンスへデータバインディングするために、propertiesプロパティを利用することができます。def save() { def b = Book.get(params.id) b.properties = params b.save() }
grails.databinding.trimStrings
property to false in grails-app/conf/Config.groovy
.grails-app/conf/Config.groovy
のgrails.databinding.trimStrings
プロパティにfalseをセットしてください。// the default value is true grails.databinding.trimStrings = false// ...
grails.databinding.convertEmptyStringsToNull
property to false in grials-app/conf/Config.groovy
.grails-app/conf/Config.groovy
のgrails.databinding.convertEmptyStringsToNull
プロパティにfalseをセットしてください。// the default value is true grails.databinding.convertEmptyStringsToNull = false// ...
trimStrings
is true
and convertEmptyStringsToNull
is true
, not only will empty Strings be converted to null but also blank Strings. A blank String is any String such that the trim()
method returns an empty String.trimStrings
がtrue
でconvertEmptyStringsToNull
がtrue
のとき、イベントの順番としては、文字列がトリムされた後にnull変換が実行されます。つまり、空文字がnullに変換されるだけでなく、空白文字列もnullに変換されることになります。
ここで、空白文字列とはtrim()
メソッドが空文字を返す文字列のことです。These forms of data binding in Grails are very convenient, but also indiscriminate. In other words, they will bind all non-transient, typed instance properties of the target object, including ones that you may not want bound. Just because the form in your UI doesn't submit all the properties, an attacker can still send malign data via a raw HTTP request. Fortunately, Grails also makes it easy to protect against such attacks - see the section titled "Data Binding and Security concerns" for more information.Grailsでのこのようなデータバインディングは大変便利ですが、同時に無差別的でもあります。 言い換えると、バインドしたくないものまで含めて、対象オブジェクトの「すべて」の非transientな型付けされたインスタンスプロパティをバインドしてしまいます。 単にUI上のフォームがすべてのプロパティをサブミットするわけではないだけであって、今もなお攻撃者は生のHTTPリクエストを使って悪意あるデータを送信することができます。 幸い、Grailsではそのような攻撃に対して防御するのも簡単です。 詳しくは、「データバインディングとセキュリティ考慮」のセクションを参照してください。
単一終端関連のデータバインディング
one-to-one
or many-to-one
association you can use Grails' data binding capability to update these relationships too. For example if you have an incoming request such as:/book/save?author.id=20
.id
suffix on the request parameter and look up the Author
instance for the given id when doing data binding such as:.id
接尾辞を自動認識して、与えられたidで該当するAuthor
インスタンスを検索してデータバインディングを行いますdef b = new Book(params)
null
by passing the literal String
"null". For example:null
をセットすることができます/book/save?author.id=null
複数終端関連のデータバインディング
Set
based association (the default for a hasMany
) then the simplest way to populate an association is to send a list of identifiers. For example consider the usage of <g:select>
below:Set
ベースの関連(hasMany
でのデフォルト)で、簡単に関連を設定するには、idのリストを送る方法が有ります。以下の例は<g:select>
を使用した方法です:<g:select name="books"
from="${Book.list()}"
size="5" multiple="yes" optionKey="id"
value="${author?.books}" />
books
association.books
の関連を設定します。<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" />
Set
based association it is critical that you render the mark-up in the same order that you plan to do the update in. This is because a Set
has no concept of order, so although we're referring to books0
and books1
it is not guaranteed that the order of the association will be correct on the server side unless you apply some explicit sorting yourself.Set
ベースの関連では、マークアップ(HTML)が描写された順序とは同じ順序で更新されない危険性あります。 これはSet
には順序という概念がなく、books[0]
やbooks[1]
と指定したとしても、サーバサイドでの関連のソートを明快に指定しない限り、順序が保証されないことになるからです。List
based associations, since a List
has a defined order and an index you can refer to. This is also true of Map
based associations.List
ベースの関連の場合は、List
の順序とインデックスを指定することができるので問題ありません。Map
ベースの関連も同じことがいえます。<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" /> <g:textField name="books[2].title" value="Red Madder" />
<g:textField name="books[0].title" value="the Stand" /> <g:textField name="books[1].title" value="the Shining" /> <g:textField name="books[5].title" value="Red Madder" />
List
using the same .id
syntax as you would use with a single-ended association. For example:List
関連でバインドするには、単一終端関連と同じように.id
シンタックスを使用します。例として:<g:select name="books[0].id" from="${bookList}" value="${author?.books[0]?.id}" /><g:select name="books[1].id" from="${bookList}" value="${author?.books[1]?.id}" />
<g:select name="books[2].id" from="${bookList}" value="${author?.books[2]?.id}" />
books List
to be selected separately.books List
の特定の場所にエントリを可能にします。<g:select name="books[0].id"
from="${Book.list()}"
value="${author?.books[0]?.id}"
noSelection="['null': '']"/>
books0
if the empty option is chosen.book[0]
の関連を削除します。Map
property works the same way except that the list index in the parameter name is replaced by the map key:Map
のプロパティも同じ方法で動作します:<g:select name="images[cover].id"
from="${Image.list()}"
value="${book?.images[cover]?.id}"
noSelection="['null': '']"/>
Map
property images
under a key of "cover"
.grails.databinding.autoGrowCollectionLimit
property in Config.groovy
.Config.groovy
のgrails.databinding.autoGrowCollectionLimit
プロパティに値を代入すれば変更できます。// grails-app/conf/Config.groovy// the default value is 256 grails.databinding.autoGrowCollectionLimit = 128
// ...
複数ドメインクラスでのデータバインディング
/book/save?book.title=The%20Stand&author.name=Stephen%20King
author.
or book.
which is used to isolate which parameters belong to which type. Grails' params
object is like a multi-dimensional hash and you can index into it to isolate only a subset of the parameters to bind.author.
またbook.
という接頭辞が付いていて、どちらの型(ドメインクラス)に属するかを区別しています。Grailsのparams
オブジェクトは多次元ハッシュのようになっており、それぞれバインドするためのパラメータサブセットとして区別されて索引されます。def b = new Book(params.book)
book.title
parameter to isolate only parameters below this level to bind. We could do the same with an Author
domain class:book.title
パラメータだけが区別されてバインドされます。ドメインクラスAuthor
も同じように指定します。def a = new Author(params.author)
データバインディングとアクション引数
class AccountingController {// accountNumber will be initialized with the value of params.accountNumber // accountType will be initialized with params.accountType def displayInvoice(String accountNumber, int accountType) { // … } }
params.accountType
request parameter has to be converted to an int
. If type conversion fails for any reason, the argument will have its default value per normal Java behavior (null for type wrapper references, false for booleans and zero for numbers) and a corresponding error will be added to the errors
property of the defining controller.params.accountType
リクエストパラメータはint
型に変換する必要があります。
もし型変換が何らかの理由により失敗した場合は、引数はJava標準的な挙動によるデフォルト値(ラッパー型はnull、boolean型はfalse、数値型は0)をとります。
そして、対応するエラーが対象コントローラのerrors
プロパティに追加されます。/accounting/displayInvoice?accountNumber=B59786&accountType=bogusValue
errors.hasErrors()
will be true, the controller's errors.errorCount
will be equal to 1 and the controller's errors.getFieldError('accountType')
will contain the corresponding error.
"bogusValue"はint型に変換できないため、accountType
は0になります。そして、コントローラのerrors.hasErrors()
はtrueに、errors.errorCount
は1になり、errors.getFieldError('accountType')
には対応するエラーが保持されます。@grails.web.RequestParameter
annotation may be applied to an argument to express the name of the request parameter which should be bound to that argument:@grails.web.RequestParameter
アノテーションを引数に付与することができます。import grails.web.RequestParameterclass AccountingController {
// mainAccountNumber will be initialized with the value of params.accountNumber // accountType will be initialized with params.accountType def displayInvoice(@RequestParameter('accountNumber') String mainAccountNumber, int accountType) { // … } }
型変換エラーとデータバインディング
class Book { … URL publisherURL }
Book
that uses the java.net.URL
class to represent URLs. Given an incoming request such as:Book
でURLを表すプロパティにjava.net.URL
クラスを使用したとします。次のようなリクエストを受信したとします:/book/save?publisherURL=a-bad-url
a-bad-url
to the publisherURL
property as a type mismatch error occurs. You can check for these like this:a-bad-url
をpublisherURL
にバインドすることはできません。次のようにしてエラーを確認します:def b = new Book(params)if (b.hasErrors()) { println "The value ${b.errors.getFieldError('publisherURL').rejectedValue}" + " is not a valid URL!" }
grails-app/i18n/messages.properties
file to use for the error. You can use a generic error message handler such as:grails-app/i18n/messages.properties
を使用してメッセージを返す事もあると思います。次のような一般的なメッセージハンドラーを使用することもできますtypeMismatch.java.net.URL=The field {0} is not a valid URL
typeMismatch.Book.publisherURL=The publisher URL you specified is not a valid URL
BindUsingアノテーション
Map
which is the data source for the data binding. The value returned from the closure will be bound to the property. The following example would result in the upper case version of the name
value in the source being applied to the name
field during data binding.Map
です。
クロージャが返す値はオブジェクトのプロパティにバインドされます。
以下の例では、データバーディングによってデータソースのname
の値が大文字に変換されたものがname
フィールドに設定されます。import org.grails.databinding.BindUsingclass SomeClass { @BindUsing({ obj, source -> source['name']?.toUpperCase() }) String name }
@BindUsing(SomeClassWhichImplementsBindingHelper) class SomeClass { String someProperty Integer someOtherProperty }
カスタムデータコンバータ
package com.myapp.convertersimport org.grails.databinding.converters.ValueConverter
/** * A custom converter which will convert String of the * form 'city:state' into an Address object. */ class AddressValueConverter implements ValueConverter {
boolean canConvert(value) { value instanceof String }
def convert(value) { def pieces = value.split(':') new com.myapp.Address(city: pieces[0], state: pieces[1]) }
Class<?> getTargetType() { com.myapp.Address } }
ValueConverter
を実装したすべてのビーンは、自動的にデータバインディングプロセスに組み込まれます。// grails-app/conf/spring/resources.groovybeans = {
addressConverter com.myapp.converters.AddressValueConverter
// ...
}
class Person { String firstName Address homeAddress }class Address { String city String state }
def person = new Person() person.properties = [firstName: 'Jeff', homeAddress: "O'Fallon:Missouri"] assert person.firstName == 'Jeff' assert person.homeAddress.city = "O'Fallon" assert person.homeAddress.state = 'Missouri'
データバインディングのためのデータフォーマット
Date
の値にバインドするとき、BindingFormatアノテーションをデータフィールドに付与することで、カスタムデータフォーマットを指定できます。import org.grails.databinding.BindingFormatclass Person { @BindingFormat('MMddyyyy') Date birthDate }
A global setting may be configured in Config.groovy
to define date formats which will be used application wide when binding to Date.
// grails-app/conf/Config.groovygrails.databinding.dateFormats = ['MMddyyyy', 'yyyy-MM-dd HH:mm:ss.S', "yyyy-MM-dd'T'hh:mm:ss'Z'"]
The formats specified in grails.databinding.dateFormats
will be attempted in the order in which they are included in the List. If a property is marked with @BindingFormat, the @BindingFormat will take precedence over the values specified in grails.databinding.dateFormats
.
The default formats that are used are "yyyy-MM-dd HH:mm:ss.S" and "yyyy-MM-dd'T'hh:mm:ss'Z'".
カスタムフォーマットコンバータ
BindingFormat
アノテーションが指定された値の文字列の大文字/小文字を変換するという、ちょっとした文字列のカスタムフォーマッタの例です。package com.myapp.convertersimport org.grails.databinding.converters.FormattedValueConverter
class FormattedStringValueConverter implements FormattedValueConverter { def convert(value, String format) { if('UPPERCASE' == format) { value = value.toUpperCase() } else if('LOWERCASE' == format) { value = value.toLowerCase() } value }
Class getTargetType() { // specifies the type to which this converter may be applied String } }
FormattedValueConverter
を実装したすべてのビーンは、自動的にデータバインディングプロセスに組み込まれます。// grails-app/conf/spring/resources.groovybeans = {
formattedStringConverter com.myapp.converters.FormattedStringValueConverter
// ...
}
BindingFormat
annotation may be applied to String fields to inform the data binder to take advantage of the custom converter.BindingFormat
アノテーションを文字列フィールドに適用できます。import org.grails.databinding.BindingFormatclass Person { @BindingFormat('UPPERCASE') String someUpperCaseString
@BindingFormat('LOWERCASE') String someLowerCaseString
String someOtherString }
Localized Binding Formats
The BindingFormat
annotation supports localized format strings by using the optional code
attribute. If a value is assigned to the code attribute that value will be used as the message code to retrieve the binding format string from the messageSource
bean in the Spring application context and that lookup will be localized.
import org.grails.databinding.BindingFormatclass Person { @BindingFormat(code='date.formats.birthdays') Date birthDate }
# grails-app/conf/i18n/messages.properties date.formats.birthdays=MMddyyyy
# grails-app/conf/i18n/messages_es.properties date.formats.birthdays=ddMMyyyy
Using The Data Binder Directly
There are situations where an application may want to use the data binder directly. For example, to do binding in a Service on some arbitrary object which is not a domain class. The following will not work because the properties
property is read only.
// src/groovy/bindingdemo/Widget.groovy package bindingdemoclass Widget { String name Integer size }
// grails-app/services/bindingdemo/WidgetService.groovy package bindingdemoclass WidgetService {
def updateWidget(Widget widget, Map data) { // this will throw an exception because // properties is read-only widget.properties = data } }
An instance of the data binder is in the Spring application context with a bean name of grailsWebDataBinder
. That bean implements the DataBinder interface. The following code demonstrates using the data binder directly.
// grails-app/services/bindingdmeo/WidgetService package bindingdemoimport org.grails.databinding.SimpleMapDataBindingSource
class WidgetService {
// this bean will be autowired into the service def grailsWebDataBinder
def updateWidget(Widget widget, Map data) { grailsWebDataBinder.bind widget, data as SimpleMapDataBindingSource }
}
See the DataBinder documentation for more information about overloaded versions
of the bind
method.
データバインディングとセキュリティ考慮
def p = Person.get(1)p.properties['firstName','lastName'] = params
firstName
and lastName
properties will be bound.firstName
とlastName
のみバインドされます。bindData
method allows the same data binding capability, but to arbitrary objects:bindData
メソッドは任意のオブジェクトに同様のデータバインディングができます:def p = new Person()
bindData(p, params)
bindData
method also lets you exclude certain parameters that you don't want updated:bindData
メソッドでは、更新したくないパラメータを除外することができます:def p = new Person()
bindData(p, params, [exclude: 'dateOfBirth'])
def p = new Person()
bindData(p, params, [include: ['firstName', 'lastName']])
Note that if an empty List is provided as a value for the空のリストがinclude
parameter then all fields will be subject to binding if they are not explicitly excluded.include
パラメータの値として与えられた場合、明示的に除外指定がなされていない限り、すべてのフィールドがバインディング対象となることに注意してください。
7.1.7 XMLとJSONのレスポンス
renderメソッドでXMLを出力する Using the render method to output XML
render
method can be passed a block of code to do mark-up building in XML:render
メソッドに、マークアップビルダーのコードブロックを渡してXML生成:def list() {def results = Book.list()
render(contentType: "text/xml") { books { for (b in results) { book(title: b.title) } } } }
<books> <book title="The Stand" /> <book title="The Shining" /> </books>
def list() {def books = Book.list() // naming conflict here
render(contentType: "text/xml") { books { for (b in results) { book(title: b.title) } } } }
books
which Groovy attempts to invoke as a method.renderメソッドでJSONを出力する Using the render method to output JSON
render
method can also be used to output JSON:render
メソッドが使用できます:def list() {def results = Book.list()
render(contentType: "application/json") { books = array { for (b in results) { book title: b.title } } } }
[ {"title":"The Stand"}, {"title":"The Shining"} ]
自動XMLマーシャリング Automatic XML Marshalling
grails.converters
package into your controller:grails.converters
パッケージをインポートする必要があります:import grails.converters.*
render Book.list() as XML
<?xml version="1.0" encoding="ISO-8859-1"?> <list> <book id="1"> <author>Stephen King</author> <title>The Stand</title> </book> <book id="2"> <author>Stephen King</author> <title>The Shining</title> </book> </list>
def xml = Book.list().encodeAsXML() render xml
自動JSONマーシャリング Automatic JSON Marshalling
XML
with JSON
:XML
と同じ仕組みで、JSON
の自動マーシャリングもサポートしています。単純に先のXML
をJSON
にするだけですrender Book.list() as JSON
[ {"id":1, "class":"Book", "author":"Stephen King", "title":"The Stand"}, {"id":2, "class":"Book", "author":"Stephen King", "releaseDate":new Date(1194127343161), "title":"The Shining"} ]
encodeAsJSON
to achieve the same effect.encodeAsJSON
も同じ結果になります。
7.1.8 JSONBuilder (JSONビルダー)
JSONBuilderとGrailsのバージョン JSONBuilder and Grails versions
JSONBuilder
class is used with the render
method for older applications; to use the newer/better JSONBuilder
class set the following in Config.groovy
:render
メソッドで古いJSONBuilder
を使用するようになっています。新しい良くなったJSONBuilder
を使用するにはConfig.groovy
に、次のような設定をします:grails.json.legacy.builder = false
単純なオブジェクトを描写 Rendering Simple Objects
render(contentType: "application/json") { hello = "world" }
{"hello":"world"}
JSON配列描写 Rendering JSON Arrays
render(contentType: "application/json") {
categories = ['a', 'b', 'c']
}
{"categories":["a","b","c"]}
render(contentType: "application/json") { categories = [ { a = "A" }, { b = "B" } ] }
{"categories":[ {"a":"A"} , {"b":"B"}] }
element
method to return a list as the root:element
メソッドを使用することで、リストをルートとして返す事ができます:render(contentType: "application/json") {
element 1
element 2
element 3
}
[1,2,3]
複雑なオブジェクトの描写 Rendering Complex Objects
render(contentType: "application/json") { categories = ['a', 'b', 'c'] title = "Hello JSON" information = { pages = 10 } }
{"categories":["a","b","c"],"title":"Hello JSON","information":{"pages":10}}
複雑なオブジェクトの配列 Arrays of Complex Objects
render(contentType: "application/json") { categories = [ { a = "A" }, { b = "B" } ] }
array
method to build them up dynamically:array
メソッドを使用して動的に生成する事もできます:def results = Book.list() render(contentType: "application/json") { books = array { for (b in results) { book title: b.title } } }
JSONBuilder APIに直接アクセスする Direct JSONBuilder API Access
render
method, but still want to produce JSON you can use the API directly:def builder = new JSONBuilder()def result = builder.build { categories = ['a', 'b', 'c'] title = "Hello JSON" information = { pages = 10 } }
// prints the JSON text println result.toString()
def sw = new StringWriter() result.render sw
7.1.9 ファイルアップロード
プログラムによるファイルアップロード Programmatic File Uploads
Upload Form: <br /> <g:uploadForm action="upload"> <input type="file" name="myFile" /> <input type="submit" /> </g:uploadForm>
uploadForm
tag conveniently adds the enctype="multipart/form-data"
attribute to the standard <g:form>
tag.uploadForm
タグは、通常の<g:form>
タグに、enctype="multipart/form-data"
の属性を追加します。def upload() { def f = request.getFile('myFile') if (f.empty) { flash.message = 'file cannot be empty' render(view: 'uploadForm') return }f.transferTo(new File('/some/local/dir/myfile.txt')) response.sendError(200, 'Done') }
InputStream
and so on with the MultipartFile interface.InputStream
を取得して直接ファイルを操作したりなど便利です。データバインディング経由のファイルアップロード File Uploads through Data Binding
Image
domain class:Image
というドメインクラスがあるとします:class Image { byte[] myFilestatic constraints = { // Limit upload file size to 2MB myFile maxSize: 1024 * 1024 * 2 } }
params
object in the constructor as in the example below, Grails will automatically bind the file's contents as a byte
to the myFile
property:params
をコンストラクタに渡してImageオブジェクトを生成すると、Grailsが自動的にファイルのコンテントをmyFile
プロパティにbyte[]
としてバインドします:def img = new Image(params)
byte
properties.byte[]
プロパティで指定した場合のblobのデフォルトサイズが255バイトになります。myFile
property on the image to a String type:myFile
プロパティの型をString型にすることで文字列にすることも可能です:class Image {
String myFile
}
7.1.10 コマンドオブジェクト
Grailsのコマンドオブジェクトの設計概念をサポートしています。 コマンドオブジェクトは data binding と連結して使用するクラスであり、通常ドメインクラスとして扱わないデータに対してバリデーションを可能にします。
Note: A class is only considered to be a command object when it is used as a parameter of an action.
コマンドオブジェクトの宣言 Declaring Command Objects
@grails.validation.Validateable class LoginCommand { String username String passwordstatic constraints = { username(blank: false, minSize: 6) password(blank: false, minSize: 6) } }
In this example, the command object is marked with the Validateable
annotation. The Validateable
annotation allows the definition of constraints just like in domain classes. If the command object is defined in the same source file as the controller that is using it, Grails will automatically mark it as Validateable
. It is not required that command object classes be validateable.
コマンドオブジェクトを使用する Using Command Objects
To use command objects, controller actions may optionally specify any number of command object parameters. The parameter types must be supplied so that Grails knows what objects to create and initialize.
Before the controller action is executed Grails will automatically create an instance of the command object class and populate its properties by binding the request parameters. If the command object class is marked with Validateable
then the command object will be validated. For example:
class LoginController {def login(LoginCommand cmd) { if (cmd.hasErrors()) { redirect(action: 'loginForm') return }
// work with the command object data } }
If the command object's type is that of a domain class and there is an id
request parameter then instead of invoking the domain class constructor to create a new instance a call will be made to the static get
method on the domain class and the value of the id
parameter will be passed as an argument. Whatever is returned from that call to get
is what will be passed into the controller action. This means that if there is an id
request parameter and no corresponding record is found in the database then the value of the command object will be null
.
コマンドオブジェクトと依存注入 Command Objects and Dependency Injection
@grails.validation.Validateable class LoginCommand {def loginService
String username String password
static constraints = { username validator: { val, obj -> obj.loginService.canLogin(obj.username, obj.password) } } }
loginService
bean which is injected by name from the Spring ApplicationContext
.loginService
をSpringのApplicatinContext
から名前解決でコマンドオブジェクトに注入しています。Binding The Request Body To Command Objects
When a request is made to a controller action which accepts a command object and the request contains a body, Grails will attempt to parse the body of the request based on the request content type and use the body to do data binding on the command object. See the following example.
// grails-app/controllers/bindingdemo/DemoController.groovy package bindingdemoclass DemoController {
def createWidget(Widget w) { render "Name: ${w?.name}, Size: ${w?.size}" } }
class Widget { String name Integer size }
$ curl -H "Content-Type: application/json" -d '{"name":"Some Widget","size":"42"}' localhost:8080/myapp/demo/createWidget Name: Some Widget, Size: 42 ~ $ $ curl -H "Content-Type: application/xml" -d '<widget><name>Some Other Widget</name><size>2112</size></widget>' localhost:8080/bodybind/demo/createWidget Name: Some Other Widget, Size: 2112 ~ $
Note that the body of the request is being parsed to make that work. Any attempt to read the body of the request after that will fail since the corresponding input stream will be empty. The controller action can either use a command object or it can parse the body of the request on its own (either directly, or by referring to something like request.JSON), but cannot do both.
// grails-app/controllers/bindingdemo/DemoController.groovy package bindingdemoclass DemoController {
def createWidget(Widget w) { // this will fail because it requires reading the body, // which has already been read. def json = request.JSON
// ...
} }
7.1.11 フォーム二重送信のハンドリング
<g:form useToken="true" ...>
withForm { // good request }.invalidToken { // bad request }
invalidToken
method then by default Grails will store the invalid token in a flash.invalidToken
variable and redirect the request back to the original page. This can then be checked in the view:invalidToken
にチェインしなかった場合は、デフォルトでGrailsが無効トークンをflash.invalidToken
に保持して、元のページにリクエストをリダイレクトします。これによってビューで以下のように確認できます:
<g:if test="${flash.invalidToken}"> Don't click the button twice! </g:if>
The withForm tag makes use of the session and hence requires session affinity or clustered sessions if used in a cluster.withFormメソッドはセッション(session)を使用するので、セッションの類似性またはクラスタで使用する場合はクラスタ化されたセッションが必要です。
7.1.12 シンプルタイプコンバータ
型変換メソッド Type Conversion Methods
def total = params.int('total')
int
method, and there are also methods for boolean
, long
, char
, short
and so on. Each of these methods is null-safe and safe from any parsing errors, so you don't have to perform any additional checks on the parameters.boolean
,long
,char
,short
等多数のメソッドがあります。これらのメソッドはnullセーフであり、パースエラーセーフなので、パラメータに対してのチェックを追加する必要がありません。
def total = params.int('total', 42)
attrs
parameter of GSP tags.attrs
パラメータでも、同じ仕組みの型変換機能が使用できます。複数の同一名称パラメータのハンドリング Handling Multi Parameters
?name=Bob&name=Judy
."?name=Bob&name=Judy"
等。String
iterate over each character. To avoid this problem the params object provides a list
method that always returns a list:String
の文字列が1文字ずつ参照されてしまいます。この問題を避けるために、paramsオブジェクトのlist
メソッドを使用することで毎回リストを返すことが可能です:for (name in params.list('name')) {
println name
}
7.1.13 コントローラ例外ハンドリング宣言
Grails controllers support a simple mechanism for declarative exception handling. If a controller declares a method that accepts a single argument and the argument type isjava.lang.Exception
or some subclass of java.lang.Exception
, that method will be invoked any time an action in that controller throws an exception of that type. See the following example.// grails-app/controllers/demo/DemoController.groovy package democlass DemoController {
def someAction() { // do some work }
def handleSQLException(SQLException e) { render 'A SQLException Was Handled' }
def handleBatchUpdateException(BatchUpdateException e) { redirect controller: 'logging', action: 'batchProblem' }
def handleNumberFormatException(NumberFormatException nfe) { [problemDescription: 'A Number Was Invalid'] } }
That controller will behave as if it were written something like this...
// grails-app/controllers/demo/DemoController.groovy package democlass DemoController {
def someAction() { try { // do some work } catch (BatchUpdateException e) { return handleBatchUpdateException(e) } catch (SQLException e) { return handleSQLException(e) } catch (NumberFormatException e) { return handleNumberFormatException(e) } }
def handleSQLException(SQLException e) { render 'A SQLException Was Handled' }
def handleBatchUpdateException(BatchUpdateException e) { redirect controller: 'logging', action: 'batchProblem' }
def handleNumberFormatException(NumberFormatException nfe) { [problemDescription: 'A Number Was Invalid'] } }
The exception handler method names can be any valid method name. The name is not what makes the method an exception handler, the Exception
argument type is the important part.
The exception handler methods can do anything that a controller action can do including invoking render
, redirect
, returning a model, etc. The exception handler methods are inherited into sublcasses so an application could define the exception handlers in an abstract class that multiple controllers extend from.
Exception handler methods must be present at compile time. Specifically, exception handler methods which are runtime metaprogrammed onto a controller class are not supported.
7.2 Groovyサーバーページ
grails-app/views
directory and are typically rendered automatically (by convention) or with the render method such as:render(view: "index")
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.GroovyロジックをGSPに埋め込むことも可能ですが推奨しません。マークアップとコードが混在するのは良いことではありません。ほとんどのGSPではコードを持つべきで無く、そうする必要がありません。
def show() { [book: Book.get(params.id)] }
Book
instance and create a model that contains a key called book
. This key can then be referenced within the GSP view using the name book
:Book
インスタンスを取り出して、book
というキーでモデルを作成しています。GSPでは、このキーbook
が参照できます。${book.title}
Embedding data received from user input has the risk of making your application vulnerable to an Cross Site Scripting (XSS) attack. Please read the documentation on XSS prevention for information on how to prevent XSS attacks.
7.2.1 GSPの基本
<% %>
scriptlet blocks to embed Groovy code (again this is discouraged):<% %>
をサポートしています。(しつこいですが推奨しません)<html> <body> <% out << "Hello GSP!" %> </body> </html>
<%= %>
syntax to output values:<%= %>
を使用して値を出力することもできます。<html> <body> <%="Hello GSP!" %> </body> </html>
<html> <body> <%-- This is my comment --%> <%="Hello GSP!" %> </body> </html>
Embedding data received from user input has the risk of making your application vulnerable to an Cross Site Scripting (XSS) attack. Please read the documentation on XSS prevention for information on how to prevent XSS attacks.
7.2.1.1 変数とスコープ
<% %>
brackets you can declare variables:<% %>
で変数を宣言できます。<% now = new Date() %>
<%=now%>
application
- The javax.servlet.ServletContext instanceapplicationContext
The Spring ApplicationContext instanceflash
- The flash objectgrailsApplication
- The GrailsApplication instanceout
- The response writer for writing to the output streamparams
- The params object for retrieving request parametersrequest
- The HttpServletRequest instanceresponse
- The HttpServletResponse instancesession
- The HttpSession instancewebRequest
- The GrailsWebRequest instance
application
- javax.servlet.ServletContextインスタンスapplicationContext
- Spring ApplicationContextインスタンスflash
- flash オブジェクトgrailsApplication
- GrailsApplicationインスタンスout
- 出力ストリームを書き込むレスポンスライターparams
- paramsリクエストパラメータを取得するオブジェクトrequest
- HttpServletRequestインスタンスresponse
- HttpServletResponseインスタンスsession
- HttpSessionインスタンスwebRequest
- GrailsWebRequestインスタンス
7.2.1.2 ロジックとイテレーション
<% %>
syntax you can embed loops and so on using this syntax:<% %>
シンタックスを使ってループなどを記述できます。<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>
7.2.1.3 ページディレクティブ
<%@ page import="java.awt.*" %>
<%@ page contentType="application/json" %>
7.2.1.4 エクスプレッション
<%= %>
syntax introduced earlier is rarely used due to the support for GSP expressions. A GSP expression is similar to a JSP EL expression or a Groovy GString and takes the form ${expr}
:<%= %>
シンタックスは滅多に使用しません。代わりに、JSPの式言語(JSP EL)と同じような機能をもつGSP(Groovy)表記または、${expr}
のようなGroovyのGStringを使用します。
<html> <body> Hello ${params.name} </body> </html>
${..}
block. ${…}
ブロックに記述します。Embedding data received from user input has the risk of making your application vulnerable to an Cross Site Scripting (XSS) attack. Please read the documentation on XSS prevention for information on how to prevent XSS attacks.
7.2.2 GSPタグ
The section on Tag Libraries covers how to add your own custom tag libraries.カスタムタグの追加などは、タグライブラリのセクションで解説します。
g:
. Unlike JSP, you don't specify any tag library imports. If a tag starts with g:
it is automatically assumed to be a GSP tag. An example GSP tag would look like:g:
で始まるタグです。JSPと違いタグライブラリのインポートが必要有りません。g:
接頭辞のタグは自動的にGSPタグとして用いられます。例としてGSPタグの見ためは次のようになります:<g:example />
<g:example> Hello world </g:example>
<g:example attr="${new Date()}"> Hello world </g:example>
<g:example attr="${new Date()}" attr2="[one:1, two:2, three:3]"> Hello world </g:example>
<g:example attr="${new Date()}" attr2="[one:'one', two:'two']"> Hello world </g:example>
7.2.2.1 変数とスコープ
<g:set var="now" value="${new Date()}" />
now
to the result of a GSP expression (which simply constructs a new java.util.Date
instance). You can also use the body of the <g:set>
tag to define a variable:now
にGSP表記の結果を割り当てています(単純にjava.util.Date
のインスタンスを生成しただけです)。<g:set>
タグのタグ内容に記述して定義することもできます。:<g:set var="myHTML"> Some re-usable code on: ${new Date()} </g:set>
<g:set var="bookService" bean="bookService" />
page
- Scoped to the current page (default)request
- Scoped to the current requestflash
- Placed within flash scope and hence available for the next requestsession
- Scoped for the user sessionapplication
- Application-wide scope.
page
- (デフォルト)現在のページrequest
- 現在のリクエストflash
- flashスコープに配置され、次のリクエストまで。session
- ユーザのセッションapplication
- アプリケーション全体
scope
attribute:scope
属性に指定します:<g:set var="now" value="${new Date()}" scope="request" />
7.2.2.2 ロジックとイテレーション
<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>
7.2.2.3 検索とフィルタリング
Stephen King's Books: <g:findAll in="${books}" expr="it.author == 'Stephen King'"> <p>Title: ${it.title}</p> </g:findAll>
expr
attribute contains a Groovy expression that can be used as a filter. The grep tag does a similar job, for example filtering by class:expr
属性に記述します。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>
books
variable is a collection of Book
instances. Since each Book
has a title
, you can obtain a list of Book titles using the expression books.title
. Groovy will auto-magically iterate the collection, obtain each title, and return a new list!books
はBook
インスタンスのコレクションです。全てのBook
にプロパティtitle
が有る場合、books.title
とすることでBook
のtitle
プロパティのリストを得られます。Groovyは自動的にコレクションを検索して全てのtitle
を含んだリストを返してくれます。
7.2.2.4 リンクとリソース
<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 params="[sort: 'title', order: 'asc', author: currentBook.author]" action="list">Book List</g:link>
7.2.2.5 フォームとフィールド
フォームの基礎 Form Basics
url
attribute lets you specify which controller and action to map to:url
属性にマップでコントローラアクションを指定します:<g:form name="myForm" url="[controller:'book',action:'list']">...</g:form>
myForm
that submits to the BookController
's list
action. Beyond that all of the usual HTML attributes apply.myForm
という名称で、BookController
のlist
アクションへ送信するフォームを生成します。通常のHTML属性はそのまま使用できます。フォームフィールド Form Fields
- textField - For input fields of type 'text'
- passwordField - For input fields of type 'password'
- checkBox - For input fields of type 'checkbox'
- radio - For input fields of type 'radio'
- hiddenField - For input fields of type 'hidden'
- select - For dealing with HTML select boxes
- textField - 属性typeが'text'のinputタグ用
- passwordField - 属性typeが'password'のinputタグ用
- checkBox - 属性typeが'checkbox'のinputタグ用
- radio - 属性typeが'radio'のinputタグ用
- hiddenField - 属性typeが'hidden'のinputタグ用
- select - セレクトボックス用タグ
<g:textField name="myField" value="${myValue}" />
マルチ送信ボタン Multiple Submit Buttons
<g:actionSubmit value="Some update label" action="update" />
7.2.2.6 タグをメソッドとして使用
GSPでタグをメソッドとして使用する Tags as method calls from GSPs
StreamCharBuffer
which has all of the same methods as String) instead of writing directly to the response when called as methods. For example:StreamCharBuffer
)。:Static Resource: ${createLinkTo(dir: "images", file: "logo.jpg")}
<img src="${createLinkTo(dir: 'images', file: 'logo.jpg')}" />
<img src="<g:createLinkTo dir="images" file="logo.jpg" />" />
コントローラとタグライブラリでタグをメソッドとして使用する Tags as method calls from Controllers and Tag Libraries
g:
namespace can be invoked without the prefix and a StreamCharBuffer
result is returned:g:
を持つタグは、接頭辞無しで呼び出す事で、結果にStreamCharBuffer
を返します:def imageLocation = createLinkTo(dir:"images", file:"logo.jpg").toString()
def imageLocation = g.createLinkTo(dir:"images", file:"logo.jpg").toString()
def editor = fckeditor.editor(name: "text", width: "100%", height: "400")
7.2.3 ビューとテンプレート
テンプレートの基礎 Template Basics
grails-app/views/book/_bookTemplate.gsp
:grails-app/views/books/
に _bookTemplate.gsp
として配置:<div class="book" id="${book?.id}"> <div>Title: ${book?.title}</div> <div>Author: ${book?.author?.name}</div> </div>
Use the render tag to render this template from one of the views in grails-app/views/book
:
grails-app/views/book
の配置したビューで、テンプレートを使用するには、renderタグを使います:
<g:render template="bookTemplate" model="[book: myBook]" />
model
attribute of the render
tag. If you have multiple Book
instances you can also render the template for each Book
using the render tag with a collection
attribute:Book
インスタンスを使用して、それぞれのBook
をテンプレートで描写する場合は、以下のようにrender
タグのcollection
属性にコレクションを指定します:<g:render template="bookTemplate" var="book" collection="${bookList}" />
テンプレートの共有 Shared Templates
BookController
and its views at grails-app/views/book
. However, you may want to share templates across your application.BookController
とgrails-app/views/book
階層以下のビューを対象に説明しました。テンプレートはアプリケーション全体を通して使用したいと思います。/
instead of a relative location. For example if you had a template called grails-app/views/shared/_mySharedTemplate.gsp
, you would reference it as:/(スラッシュ)
から始まる grails-app/viewsからの絶対パスで指定します。例として、テンプレートファイルが、grails-app/views/shared/_mySharedTemplate.gsp
だとした場合は次のように指定します:<g:render template="/shared/mySharedTemplate" />
<g:render template="/book/bookTemplate" model="[book: myBook]" />
テンプレートネームスペース The Template Namespace
tmpl
, available that makes using templates easier. Consider for example the following usage pattern:tmpl
という名称の、テンプレートネームスペースが使用できます:<g:render template="bookTemplate" model="[book:myBook]" />
This can be expressed with the tmpl
namespace as follows:
上記の例をtmpl
ネームスペースで記述すると以下のようになります:
<tmpl:bookTemplate book="${myBook}" />
コントローラとタグライブラリでのテンプレート Templates in Controllers and Tag Libraries
def bookData() {
def b = Book.get(params.id)
render(template:"bookTemplate", model:[book:b])
}
def bookData() { def b = Book.get(params.id) String content = g.render(template:"bookTemplate", model:[book:b]) render content }
g
namespace which tells Grails we want to use the tag as method call instead of the render method.g
を使用することでタグをメソッドとして使用するのか、コントローラのrenderメソッドなのかを区別しています。
7.2.4 Sitemeshでレイアウト
レイアウトを作成する Creating Layouts
grails-app/views/layouts
directory. A typical layout can be seen below:grails-app/views/layouts
ディレクトリに配置します。標準的なレイアウトは以下のようになります:<html> <head> <title><g:layoutTitle default="An example decorator" /></title> <g:layoutHead /> </head> <body onload="${pageProperty(name:'body.onload')}"> <div class="menu"><!--my common menu goes here--></menu> <div class="body"> <g:layoutBody /> </div> </div> </body> </html>
layoutTitle
- outputs the target page's titlelayoutHead
- outputs the target page's head tag contentslayoutBody
- outputs the target page's body tag contents
layoutTitle
- ページのタイトルを出力layoutHead
- ページのheadタグコンテントを出力layoutBody
- ページのbodyタグコンテントを出力
レイアウトを反映させる Triggering Layouts
<html> <head> <title>An Example Page</title> <meta name="layout" content="main" /> </head> <body>This is my content!</body> </html>
grails-app/views/layouts/main.gsp
will be used to layout the page. If we were to use the layout from the previous section the output would resemble this:grails-app/views/layouts/main.gsp
が呼び出されてページのレイアウトに使用されます。内容が先ほどのレイアウトの内奥であれば、以下のような出力が得られます:<html> <head> <title>An Example Page</title> </head> <body onload=""> <div class="menu"><!--my common menu goes here--></div> <div class="body"> This is my content! </div> </body> </html>
コントローラでレイアウトを指定 Specifying A Layout In A Controller
class BookController { static layout = 'customer'def list() { … } }
grails-app/views/layouts/customer.gsp
which will be applied to all views that the BookController
delegates to. The value of the "layout" property may contain a directory structure relative to the grails-app/views/layouts/
directory. For example:grails-app/views/layouts/customer.gspと
いうレイアウトファイルを作成すると、BookController
に連動する全てのビューに反映します。
"layout"プロパティの値はgrails-app/views/layouts/
ディレクトリが含まれます。例として:class BookController { static layout = 'custom/customer'def list() { … } }
Views rendered from that controller would be decorated with the grails-app/views/layouts/custom/customer.gsp
template.
この例で、このコントローラのビューは、grails-app/views/layouts/custom/customer.gsp
テンプレートでデコレートされます。
慣習によるレイアウト Layout by Convention
class BookController { def list() { … } }
grails-app/views/layouts/book.gsp
, which will be applied to all views that the BookController
delegates to.BookController
のビューに反映されるレイアウトgrails-app/views/layouts/book.gsp
を作成できます。grails-app/views/layouts/book/list.gsp
which will only be applied to the list
action within the BookController
.BookController
のアクションlist
のみに反映するレイアウトgrails-app/views/layouts/book/list.gsp
を作成できます。grails-app/views/layouts/application.gsp
. The name of the application default layout may be changed by defining a property
in grails-app/conf/Config.groovy
as follows:grails-app/views/layouts/application.gsp
を探しに行きます。アプリケーションのデフォルトレイアウト名称は、次のプロパティをgrails-app/conf/Config.groovy
に定義することで変更が可能です。:grails.sitemesh.default.layout = 'myLayoutName'
grails-app/views/layouts/myLayoutName.gsp
.grails-app/views/layouts/myLayoutName.gsp
になります。インラインレイアウト Inline Layouts
<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 Includes
<g:include controller="book" action="list" />
<g:applyLayout name="myLayout"> <g:include controller="book" action="list" /> </g:applyLayout>
def content = include(controller:"book", action:"list")
7.2.5 静的リソース
- Web application performance tuning is difficult
- Correct ordering of resources, and deferred inclusion of JavaScript
- Resources that depend on others that must be loaded first
- The need for a standard way to expose static resources in plugins and applications
- The need for an extensible processing chain to optimize resources
- Preventing multiple inclusion of the same resource
- Webアプリケーションのパフォーマンスチューニングが難しい
- リソースの配置順と、JavaScriptの遅延包括
- 先に読み込む必要のある依存関係のあるリソース
- プラグインやアプリケーションでの、静的リソースを表示する方法の標準化が必要
- リソースの最適化を行うための拡張可能な連鎖行程が必要
- 同じリソースの多重配置を防ぐ
7.2.5.1 リソースタグでのリソースの使用
r:requireでリソースを制御する Pulling in resources with r:require
<html> <head> <r:require module="jquery"/> <r:layoutResources/> </head> <body> … <r:layoutResources/> </body> </html>
r:require
multiple times in a GSP page, and you use the "modules" attribute to provide a list of modules:r:require
を複数回呼んでも構いません。そして"modules"属性を使用してモジュールを列挙することもできます:<html> <head> <r:require modules="jquery, main, blueprint, charting"/> <r:layoutResources/> </head> <body> … <r:layoutResources/> </body> </html>
<r:layoutResources/>
タグが必要になります。r:layoutResourcesでリソースへのリンクを描写する Rendering the links to resources with r:layoutResources
<html> <head> <g:layoutTitle/> <r:layoutResources/> </head> <body> <g:layoutBody/> <r:layoutResources/> </body> </html>
r:scriptでページ固有のJavaScriptコードを追加 Adding page-specific JavaScript code with r:script
r:script
directly when you need to include fragments of JavaScript code.r:script
を呼ぶことを推奨します。<html> <head> <g:layoutTitle/> <r:layoutResources/> </head> <body> <g:layoutBody/> <r:layoutResources/> </body> </html>
<html> <head> <title>Testing r:script magic!</title> </head> <body> <r:script disposition="head"> window.alert('This is at the end of <head>'); </r:script> <r:script disposition="defer"> window.alert('This is at the end of the body, and the page has loaded.'); </r:script> </body> </html>
r:imgで画像へのリンク Linking to images with r:img
<img>
markup, using the Resources framework to process the resource on the fly (if configured to do so - e.g. make it eternally cacheable).<img>
tag if the resource has been previously declared in a module.<img>
タグの属性に追加します。<html> <head> <title>Testing r:img</title> </head> <body> <r:img uri="/images/logo.png"/> </body> </html>
g:img
tag as a shortcut for rendering <img>
tags that refer to a static resource. The Grails img tag is Resources-aware and will delegate to r:img
if found. However it is recommended that you use r:img
directly if using the Resources plugin.<img>
タグを描写する、リソース参照できるg:img
タグが存在します。Grailsのimgタグは、存在した場合リソースフレームワークが認識してr:img
に委譲しますが、リソースプラグインを使用している場合は直接r:img
を使用することを推奨します。7.2.5.2 他のリソースタグ
r:resource
g:resource
tag delegates to this implementation if found, but if your code requires the Resources plugin, you should use r:resource
directly.g:resource
タグが有った場合はこのタグに委譲します。リソースプラグインを使用している場合は直接r:resource
を使用しましょう。
r:external
7.2.5.3 リソースの宣言
Config.groovy
in the case of application-specific resources, or more commonly in a resources artefact in grails-app/conf
.Config.groovy
ファイルに記述、また通常はリソースアーテファクトとしてgrails-app/conf
にファイルで配置します。grails-app/conf/MyAppResources.groovy
:grails-app/conf/MyAppResources.groovy
ファイルで例として有るとします:modules = { core { dependsOn 'jquery, utils'resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js' resource url: '/css/main.css', resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }
utils { dependsOn 'jquery'
resource url: '/js/utils.js' }
forms { dependsOn 'core,utils'
resource url: '/css/forms.css' resource url: '/js/forms.js' } }
bundle:'someOtherName'
on each resource, or call defaultBundle
on the module (see resources plugin documentation).bundle:'someOtherName'
またはモジュールでdefaultBundle
を呼ぶことででオーバーライドできます。(Resourcesプラグインドキュメントを参照)dependsOn
, which controls the load order of the resources.dependsOn
で宣言しています。<r:require module="forms"/>
in your GSP, it will pull in all the resources from 'core' and 'utils' as well as 'jquery', all in the correct order.<r:require module="forms"/>
を含んだ場合、この例では'core','util'そして'jquery'のリソースを正常な順番で引っ張ります。disposition:'head'
on the core.js
file. This tells Resources that while it can defer all the other JS files to the end of the body, this one must go into the <head>
.disposition:'head'
に注目してください。その他全てのリソースはボディの最後に配置して、このファイルは必ず<head>
に配置する指定をしています。attrs
map option, and these are passed through to the r:external
tag when the engine renders the link to the resource, so you can customize the HTML attributes of the generated link.attrs
マップで追加しています。そしてこれらはr:external
タグを通してリソースへのリンクを形成します。これによってリンクのHTML属性をカスタマイズすることができます。grails.resources.modules
Config variable.grails.resources.modules
に定義します。7.2.5.4 プラグインリソースのオーバーライド
defaultBundle
setting for a module, or attributes of individual resources that have been declared with a unique id:defaultBundle
設定で定義した名称を指定する、DSLの"overrides"で対応しています。modules = { core { dependsOn 'jquery, utils' defaultBundle 'monolith'resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js' resource url: '/css/main.css', resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }
utils { dependsOn 'jquery' defaultBundle 'monolith'
resource url: '/js/utils.js' }
forms { dependsOn 'core,utils' defaultBundle 'monolith'
resource url: '/css/forms.css' resource url: '/js/forms.js' }
overrides { jquery { defaultBundle 'monolith' } } }
For full details of the resource DSL please see the resources plugin documentation. リソースDSLの詳細はリソースプラグインのドキュメントを参考にしてください。
7.2.5.5 リソースの最適化
複数のリソースを少ないファイルにまとめる Bundling multiple resources into fewer files
defaultBundle
clause on the module. Modules have an implicit default bundle name the same as the name of the module.defaultBundle
に継承されたリソースに対してデフォルトで操作します。モジュールはモジュール名と同じ名称で、暗黙なデフォルトバンドル名を持っています。modules = { core { dependsOn 'jquery, utils' defaultBundle 'common'resource url: '/js/core.js', disposition: 'head' resource url: '/js/ui.js', bundle: 'ui' resource url: '/css/main.css', bundle: 'theme' resource url: '/css/branding.css' resource url: '/css/print.css', attrs: [media: 'print'] }
utils { dependsOn 'jquery'
resource url: '/js/utils.js', bundle: 'common' }
forms { dependsOn 'core,utils'
resource url: '/css/forms.css', bundle: 'ui' resource url: '/js/forms.js', bundle: 'ui' } }
クライアントのブラウザでリソースを"永久的に"キャッシュさせる Making resources cache "eternally" in the client browser
リソース(Zip)圧縮 Zipping resources
最小化(Minifying) Minifying
7.2.5.6 デバッグ
X-Grails-Resources-Original-Srcヘッダー X-Grails-Resources-Original-Src Header
デバッグフラグを追加 Adding the debug flag
常にデバッグを有効にする Turning on debug all the time
grails.resources.debug = true
7.2.5.7 リソース生成・変換処理を防ぐ
個々のリソースの特定のマッパー処理を防ぐ Preventing the application of a specific mapper to an individual resource
modules = { forms { resource url: '/css/forms.css', nohashandcache: true resource url: '/js/forms.js', nohashandcache: true } }
特定のマッパーでの、パス、ファイルタイプの除外と包含 Excluding/including paths and file types from specific mappers
Config.groovy
using the mapper name e.g:Config.groovy
にマッパー名で定義することができます:// We wouldn't link to .exe files using Resources but for the sake of example: grails.resources.zip.excludes = ['**/*.zip', '**/*.exe']// Perhaps for some reason we want to prevent bundling on "less" CSS files: grails.resources.bundle.excludes = ['**/*.less']
"ad-hoc"(レガシー)リソースとして扱う物の操作 Controlling what is treated as an "ad-hoc" (legacy) resource
grails.resources.adhoc.patterns = ['images/*', '*.js', '*.css']
7.2.5.8 リソース関連のプラグイン
7.2.6 Sitemeshコンテントブロック
<content>
tag:<content>
タグでページ内をブロック分割します。<content tag="navbar"> … ここにナビゲーションバーコンテンツ… </content><content tag="header"> … ここにヘッダーコンテンツ… </content>
<content tag="footer"> … ここにフッターコンテンツ… </content>
<content tag="body"> … ここにボディコンテンツ… </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>
7.2.7 デプロイされたアプリケーションへの変更
grails.gsp.view.dir
configuration setting.grails.gsp.view.dir
定義という解決方法が存在します。/var/www/grails/my-app
directory. We add these two lines to grails-app/conf/Config.groovy
:/var/www/grails/my-app
の階層はパックしないこととします。以下の2行をgrails-app/conf/Config.groovy
に追加します:grails.gsp.enable.reload = true grails.gsp.view.dir = "/var/www/grails/my-app/"
The trailing slash on thegrails.gsp.view.dir
value is important! Without it, Grails will look for views in the parent directory.grails.gsp.view.dir
の最後に付いているスラッシュは重要です。これが無いとGrailsが親ディレクトリにビューを探しに行きます。
mkdir -p /var/www/grails/my-app/grails-app/views cp -R grails-app/views/* /var/www/grails/my-app/grails-app/views
grails-app/views
bit. So you end up with the path /var/www/grails/my-app/grails-app/views/...
.grails-app/views
を含んだ全てのビューディレクトリ階層を持たなくてはならない所です。この場合だとパスは、/var/www/grails/my-app/grails-app/views/...
となります。Name | Description | Default |
---|---|---|
grails.gsp.enable.reload | altervative system property for enabling the GSP reload mode without changing Config.groovy | |
grails.gsp.reload.interval | interval between checking the lastmodified time of the gsp source file, unit is milliseconds | 5000 |
grails.gsp.reload.granularity | the number of milliseconds leeway to give before deciding a file is out of date. this is needed because different roundings usually cause a 1000ms difference in lastmodified times | 1000 |
名称 | 説明 | 既存値 |
---|---|---|
grails.gsp.enable.reload | Config.groovy変更せずにGSPリロードモードを許可にします。 | |
grails.gsp.reload.interval | gspソースファイルの更新を確認するインターバル。ミリ秒で指定。 | 5000 |
grails.gsp.reload.granularity | ファイルが更新されたかを判断する時間のゆとり。ミリ秒で指定。 | 1000 |
7.2.8 GSPデバッグ
生成されたソースコードを表示する Viewing the generated source code
- Adding "?showSource=true" or "&showSource=true" to the url shows the generated Groovy source code for the view instead of rendering it. It won't show the source code of included templates. This only works in development mode
- The saving of all generated source code can be activated by setting the property "grails.views.gsp.keepgenerateddir" (in Config.groovy) . It must point to a directory that exists and is writable.
- During "grails war" gsp pre-compilation, the generated source code is stored in grails.project.work.dir/gspcompile (usually in ~/.grails/(grails_version)/projects/(project name)/gspcompile).
- URLに、 "?showSource=true" または、"&showSource=true"を追加するとページ描写の代わりに、ビュー用に生成されたGroovyコードが参照できます。但し含んだテンプレートのコードは含まれません。開発モードのみで動作します。
- Config.groovyにプロパティ"grails.views.gsp.keepgenerateddir"を指定すると生成ファイルが指定された場所に保存されます。
- "grails war"でのgspプリコンパイル時に、生成されたソースコードがgrails.project.work.dir/gspcompileに保存されます。(通常は~/.grails/(grails_version)/projects/(project name)/gspcompileです。)
デバッガでGSPコードをデバッグ Debugging GSP code with a debugger
使用されたテンプレートの情報を参照 Viewing information about templates used to render a single url
g:render
taglib. Several small templates can be used to render a single page.
It might be hard to find out what GSP template actually renders the html seen in the result.
The debug templates -feature adds html comments to the output. The comments contain debug information about gsp templates used to render the page.g:render
タグを使用してGSPテンプレートは再利用されます。幾つかの小さなテンプレートが1つのページで使われる事もあります。どの部分でGSPテンプレートが実際にHTMLを描写したか探すのは大変だと思います。テンプレートデバッグ機能ではhtmlコメントでGSPテンプレートのデバッグ情報を提供します。
<!-- GSP #2 START template: /home/.../views/_carousel.gsp
precompiled: false lastmodified: … -->
.
.
.
<!-- GSP #2 END template: /home/.../views/_carousel.gsp
rendering time: 115 ms -->
7.3 タグライブラリ
TagLib
and place it within the grails-app/taglib
directory:grails-app/taglib
ディレクトリに、名称の最後がTagLib
となるGroovyクラスを作成します:class SimpleTagLib {}
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:
引数attrs
はタグ属性のMapです。引数body
はタグ内容のコンテンツを呼び出すクロージャです:
class SimpleTagLib { def emoticon = { attrs, body -> out << body() << (attrs.happy == 'true' ? " :-)" : " :-(") } }
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:Writer
を参照する変数out
に対してコンテンツを追加しています。ここまで作成したら、以下のように、GSPの中でタグをそのまま参照します。importは必要ありません。:<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タグリブはGroovyコードを使用しているため、タグで使用可能な属性全てを認識することは困難です。SpringSource Tool Suite (STS)等のIDEでの補完機能に対しての手助けとして、タグクロージャのJavadocコメント内に@attr
descriptions. Since taglibs use Groovy code it can be difficult to reliably detect all usable attributes.@attr
を含めましょう。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.必須条件の属性には、キーワード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) } }
7.3.1 変数とスコープ
actionName
- The currently executing action namecontrollerName
- The currently executing controller nameflash
- The flash objectgrailsApplication
- The GrailsApplication instanceout
- The response writer for writing to the output streampageScope
- A reference to the pageScope object used for GSP rendering (i.e. the binding)params
- The params object for retrieving request parameterspluginContextPath
- The context path to the plugin that contains the tag libraryrequest
- The HttpServletRequest instanceresponse
- The HttpServletResponse instanceservletContext
- The javax.servlet.ServletContext instancesession
- The HttpSession instance
actionName
- 現在実行中のアクション名controllerName
- 現在実行中のコントローラ名flash
- flashオブジェクトgrailsApplication
- GrailsApplicationインスタンスout
- 出力用のレスポンスライターpageScope
- GSP描写で使用するpageScopeオブジェクトへの参照params
- リクエストパラメータ取得用のparamsオブジェクトpluginContextPath
- タグライブラリを含むプラグインへのコンテキストパス。request
- HttpServletRequestインスタンスresponse
- HttpServletResponseインスタンスservletContext
- javax.servlet.ServletContext インスタンスsession
- HttpSessionインスタンス
7.3.2 簡単なタグ
dateFormat
style tag:dateFormat
タグを紹介します:def dateFormat = { attrs, body ->
out << new java.text.SimpleDateFormat(attrs.format).format(attrs.date)
}
SimpleDateFormat
class to format a date and then write it to the response. The tag can then be used within a GSP as follows:SimpleDateFormat
クラスを使ってフォーマットした日付をレスポンスに出力します。このタグは、GSPで次のように使用します:
<g:dateFormat format="dd-MM-yyyy" date="${new Date()}" />
def formatBook = { attrs, body -> out << "<div id="${attrs.book.id}">" out << "Title : ${attrs.book.title}" out << "</div>" }
def formatBook = { attrs, body ->
out << render(template: "bookTemplate", model: [book: attrs.book])
}
7.3.3 制御タグ
def isAdmin = { attrs, body ->
def user = attrs.user
if (user && checkUserPrivs(user)) {
out << body()
}
}
<g:isAdmin user="${myUser}"> // some restricted content </g:isAdmin>
7.3.4 イテレートタグ
def repeat = { attrs, body -> attrs.times?.toInteger()?.times { num -> out << body(num) } }
times
attribute and if it exists convert it to a number, then use Groovy's times
method to iterate the specified number of times:times
を有無や数値なのかを確認し、その数値でGroovyのtimes
メソッドを実行して、与えられた数値の回数、実行内容を繰り返します:<g:repeat times="3"> <p>Repeat this 3 times! Current repeat = ${it}</p> </g:repeat>
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:it
の使い方を紹介しています。これはbody実行時にイテレーションの数値をbodyに渡すことで動作しています。:out << body(num)
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:it
に渡されます。ただしタグをネストした場合は名称の衝突が起きるため違う名称を指定できるようにする必要があります。:def repeat = { attrs, body -> def var = attrs.var ?: "num" attrs.times?.toInteger()?.times { num -> out << body((var):num) } }
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.変数名パーレン使用時の注意点です。これを省略するとGroovyは文字列キーとして解釈してしまい変数として参照できません。
<g:repeat times="3" var="j"> <p>Repeat this 3 times! Current repeat = ${j}</p> </g:repeat>
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.var
属性に指定した変数名j
をタグ内部で変数として参照することができます。
7.3.5 タグネームスペース
g:
prefix in GSP pages. However, you can specify a different namespace by adding a static property to your TagLib
class:g:
接頭辞が使用できます。ネームスペースはstaticプロパティnamespace
をTagLib
クラスに追加する事で、独自に定義することが可能です。:class SimpleTagLib { static namespace = "my"def example = { attrs -> … } }
namespace
of my
and hence the tags in this tag lib must then be referenced from GSP pages like this:namespace
をmy
と定義したので、このタグを参照するにはGSPページ内では以下のように参照できます:<my:example name="..." />
namespace
property. Namespaces are particularly useful for plugins.namespace
プロパティに設定したものが接頭辞として使用しています。ネームスペースはプラグインでタグ提供を行う際に有用です。out << my.example(name:"foo")
7.3.6 JSPタグライブラリの使用
taglib
directive:taglib
ディレクティブを定義します。<%@ taglib prefix="fmt" uri="http://java.sun.com/jsp/jstl/fmt" %>
<fmt:formatNumber value="${10}" pattern=".00"/>
${fmt.formatNumber(value:10, pattern:".00")}
7.3.7 タグの戻り値
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.org.codehaus.groovy.grails.web.util.StreamCharBuffer
クラスのインスタンスを返します。
この実装でリクエスト処理時のバッファリング最適化や、オブジェクト生成の減少によりパフォーマンスの向上が行われました。
それ以前のGrailsではjava.lang.String
インスタンスを返します。returnObjectForTags
property in the tag library class.returnObjectForTags
プロパティにタグ名称をリストします。class ObjectReturningTagLib { static namespace = "cms" static returnObjectForTags = ['content']def content = { attrs, body -> CmsContent.findByCode(attrs.code)?.content } }
7.4 URLマッピング
/controller/action/id
. However, this convention is not hard wired into Grails and is in fact controlled by a URL Mappings class located at grails-app/conf/UrlMappings.groovy
./controller/action/id
を使用しています。このルールはGrailsで必須になっているわけでは無く、grails-app/conf/UrlMappings.groovy
に配置されているURLマッピングクラスでコントロールされています。UrlMappings
class contains a single property called mappings
that has been assigned a block of code:URLMappings
クラスは、コードブロックが定義されたmappings
というプロパティを持っています。:class UrlMappings {
static mappings = {
}
}
7.4.1 コントローラとアクションにマッピング
"/product"(controller: "product", action: "list")
/product
to the list
action of the ProductController
. Omit the action definition to map to the default action of the controller:/product
は、ProductController
のlist
アクションにマップ定義したことになります。アクションを省略すると、コントローラのデフォルトアクションにマップ定義できます:"/product"(controller: "product")
"/product" { controller = "product" action = "list" }
If you have mappings that all fall under a particular path you can group mappings with the group
method:
group "/product", { "/apple"(controller:"product", id:"apple") "/htc"(controller:"product", id:"htc") }
"/hello"(uri: "/hello.dispatch")
7.4.2 RESTリソースへのマッピング
Since Grails 2.3, it possible to create RESTful URL mappings that map onto controllers by convention. The syntax to do so is as follows:"/books"(resources:'book')
You define a base URI and the name of the controller to map to using the resources
parameter. The above mapping will result in the following URLs:
HTTP Method | URI | Grails Action |
---|---|---|
GET | /books | index |
GET | /books/create | create |
POST | /books | save |
GET | /books/${id} | show |
GET | /books/${id}/edit | edit |
PUT | /books/${id} | update |
DELETE | /books/${id} | delete |
If you wish to include or exclude any of the generated URL mappings you can do so with the includes
or excludes
parameter, which accepts the name of the Grails action to include or exclude:
"/books"(resources:'book', excludes:['delete', 'update'])or
"/books"(resources:'book', includse:['index', 'show'])
Single resources
A single resource is a resource for which there is only one (possibly per user) in the system. You can create a single resource using the resource
parameter (as oppose to resources
):
"/book"(resource:'book')
This results in the following URL mappings:
HTTP Method | URI | Grails Action |
---|---|---|
GET | /book/create | create |
POST | /book | save |
GET | /book | show |
GET | /book/edit | edit |
PUT | /book | update |
DELETE | /book | delete |
The main difference is that the id is not included in the URL mapping.
Nested Resources
You can nest resource mappings to generate child resources. For example:
"/books"(resources:'book') { "/authors"(resources:"author") }
The above will result in the following URL mappings:
HTTP Method | URL | Grails Action |
---|---|---|
GET | /books/${bookId}/authors | index |
GET | /books/${bookId}/authors/create | create |
POST | /books/${bookId}/authors | save |
GET | /books/${bookId}/authors/${id} | show |
GET | /books/${bookId}/authors/edit/${id} | edit |
PUT | /books/${bookId}/authors/${id} | update |
DELETE | /books/${bookId}/authors/${id} | delete |
You can also nest regular URL mappings within a resource mapping:
"/books"(resources: "book") { "/publisher"(controller:"publisher") }
This will result in the following URL being available:
HTTP Method | URL | Grails Action |
---|---|---|
GET | /books/1/publisher | index |
Linking to RESTful Mappings
You can link to any URL mapping created with the g:link
tag provided by Grails simply by referencing the controller and action to link to:
<g:link controller="book" action="index">My Link</g:link>
As a convenience you can also pass a domain instance to the resource
attribute of the link
tag:
<g:link resource="${book}">My Link</g:link>
This will automatically produce the correct link (in this case "/books/1" for an id of "1").
The case of nested resources is a little different as they typically required two identifiers (the id of the resource and the one it is nested within). For example given the nested resources:
"/books"(resources:'book') { "/authors"(resources:"author") }
If you wished to link to the show
action of the author
controller, you would write:
// Results in /books/1/authors/2 <g:link controller="author" action="show" method="GET" params="[bookId:1]" id="2">The Author</g:link>
However, to make this more concise there is a resource
attribute to the link tag which can be used instead:
// Results in /books/1/authors/2 <g:link resource="book/author" action="show" bookId="1" id="2">My Link</g:link>
The resource attribute accepts a path to the resource separated by a slash (in this case "book/author"). The attributes of the tag can be used to specify the necessary bookId
parameter.
7.4.3 URLマッピングでのリダイレクト
Since Grails 2.3, it is possible to define URL mappings which specify a redirect. When a URL mapping specifies a redirect, any time that mapping matches an incoming request, a redirect is initiated with information provided by the mapping.When a URL mapping specifies a redirect the mapping must either supply a String
representing a URI to redirect to or must provide a Map representing the target
of the redirect. That Map is structured just like the Map that may be passed
as an argument to the redirect
method in a controller.
"/viewBooks"(redirect: '/books/list') "/viewAuthors"(redirect: [controller: 'author', action: 'list']) "/viewPublishers"(redirect: [controller: 'publisher', action: 'list', permanent: true])
Request parameters that were part of the original request will be included in the redirect.
7.4.4 埋込変数
簡単な変数 Simple Variables
/product
. However, in many circumstances you don't know what the value of a particular token will be until runtime. In this case you can use variable placeholders within the URL for example:/product
のように適切に指定できますが、状況によっては、ランタイム時にしか一部のトークンが予測できない場合があります。その場合は以下の例のように変数を指定できます:static mappings = { "/product/$id"(controller: "product") }
id
. For example given the URL /product/MacBook
, the following code will render "MacBook" to the response:id
のパラメータとしてマップします(このパラメータはparamsオブジェクトから取得できます)。例として、URL /product/MacBook
へアクセスを行うと、次のコードでは、結果として"MacBook"をレスポンスします:class ProductController { def index() { render params.id } }
static mappings = { "/$blog/$year/$month/$day/$id"(controller: "blog", action: "show") }
/graemerocher/2007/01/10/my_funky_blog_entry
year
, month
, day
, id
and so on. blog
, year
, month
, day
, id
として値を取得する事ができます。動的なコントローラ名とアクション名 Dynamic Controller and Action Names
static mappings = { "/$controller/$action?/$id?"() }
controller
, action
and id
embedded within the URL.controller
,action
,id
を取得して、コントローラ、アクション、idを確定します。static mappings = { "/$controller" { action = { params.goHere } } }
省略可能な変数 Optional Variables
static mappings = { "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show") }
/graemerocher/2007/01/10/my_funky_blog_entry
/graemerocher/2007/01/10
/graemerocher/2007/01
/graemerocher/2007
/graemerocher
Optional File Extensions
If you wish to capture the extension of a particular path, then a special case mapping exists:
"/$controller/$action?/$id?(.$format)?"()
By adding the (.$format)?
mapping you can access the file extension using the response.format
property in a controller:
def index() {
render "extension is ${response.format}"
}
任意変数 Arbitrary Variables
"/holiday/win" { id = "Marrakech" year = 2007 }
動的解決変数 Dynamically Resolved Variables
"/holiday/win" { id = { params.id } isEligible = { session.user != null } // must be logged in }
7.4.5 ビューへマッピング
/
to a GSP at the location grails-app/views/index.gsp
you could use:/
を、grails-app/views/index.gsp
にマッピングするには:static mappings = { "/"(view: "/index") // map the root URL }
static mappings = { "/help"(controller: "site", view: "help") // to a view for a controller }
7.4.6 レスポンスコードへマッピング
static mappings = { "403"(controller: "errors", action: "forbidden") "404"(controller: "errors", action: "notFound") "500"(controller: "errors", action: "serverError") }
static mappings = { "403"(view: "/errors/forbidden") "404"(view: "/errors/notFound") "500"(view: "/errors/serverError") }
エラーハンドリング宣言 Declarative Error Handling
static mappings = { "403"(view: "/errors/forbidden") "404"(view: "/errors/notFound") "500"(controller: "errors", action: "illegalArgument", exception: IllegalArgumentException) "500"(controller: "errors", action: "nullPointer", exception: NullPointerException) "500"(controller: "errors", action: "customException", exception: MyException) "500"(view: "/errors/serverError") }
IllegalArgumentException
will be handled by the illegalArgument
action in ErrorsController
, a NullPointerException
will be handled by the nullPointer
action, and a MyException
will be handled by the customException
action. Other exceptions will be handled by the catch-all rule and use the /errors/serverError
view.IllegalArgumentException
はErrorsControlle
rのillegalArgument
、NullPointerException
はnullPointer
アクション、MyException
はcustomException
アクションでそれぞれ処理されます。その他の例外がcatch-allルールでハンドルされビューに/errors/serverError
を使用します。exception
attribute like so:exception
を使用します:class ErrorController { def handleError() { def exception = request.exception // perform desired processing to handle the exception } }
If your error-handling controller action throws an exception as well, you'll end up with aエラーハンドリング用のコントローラアクションが例外を出した場合は、最終的にStackOverflowException
.StackOverflowException
になります。
7.4.7 HTTPメソッドへマッピング
ProductController
:ProductController
用にRESTful API URLマッピングを提供しています:static mappings = { "/product/$id"(controller:"product") { action = [GET:"show", PUT:"update", DELETE:"delete", POST:"save"] } }
7.4.8 マッピングワイルドカード
static mappings = { "/images/*.jpg"(controller: "image") }
/image/logo.jpg
. Of course you can achieve the same effect with a variable:/image/logo.jpg
のような画像パスにマッチします。変数も使用可能です:static mappings = { "/images/$name.jpg"(controller: "image") }
static mappings = { "/images/**.jpg"(controller: "image") }
/image/logo.jpg
as well as /image/other/logo.jpg
. Even better you can use a double wildcard variable:/image/logo.jpg
や、/image/other/logo.jpg
にマッチします。ダブルワイルドカード変数も使用できます:static mappings = { // will match /image/logo.jpg and /image/other/logo.jpg "/images/$name**.jpg"(controller: "image") }
name
parameter obtainable from the params object:name
パラメータにマッチしたパスをparamsオブジェクトから取得できます:def name = params.name println name // prints "logo" or "other/logo"
excludes
setting inside the UrlMappings.groovy
class:UrlMappings.groovy
クラスで、excludes
を指定することで可能です:class UrlMappings { static excludes = ["/images/*", "/css/*"] static mappings = { … } }
/images
or /css
./images
また/css
で始まるURIはURLマッピングから除外されます.
7.4.9 自動リンクリライト
static mappings = { "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show") }
<g:link controller="blog" action="show" params="[blog:'fred', year:2007]"> My Blog </g:link><g:link controller="blog" action="show" params="[blog:'fred', year:2007, month:10]"> My Blog - October 2007 Posts </g:link>
<a href="/fred/2007">My Blog</a> <a href="/fred/2007/10">My Blog - October 2007 Posts</a>
7.4.10 制約の適用
static mappings = { "/$blog/$year?/$month?/$day?/$id?"(controller:"blog", action:"show") }
/graemerocher/2007/01/10/my_funky_blog_entry
/graemerocher/not_a_year/not_a_month/not_a_day/my_funky_blog_entry
"/$blog/$year?/$month?/$day?/$id?" { controller = "blog" action = "show" constraints { year(matches:/\d{4}/) month(matches:/\d{2}/) day(matches:/\d{2}/) } }
year
, month
and day
parameters match a particular valid pattern thus relieving you of that burden later on.year
,month
,day
パラメータが特定のパターンにマッチするように制約を定義しています。
7.4.11 名前付きURLマッピング
static mappings = {
name <mapping name>: <url pattern> {
// …
}
}
static mappings = { name personList: "/showPeople" { controller = 'person' action = 'list' } name accountDetails: "/details/$acctNumber" { controller = 'product' action = 'accountDetails' } }
<g:link mapping="personList">List People</g:link>
<a href="/showPeople">List People</a>
<g:link mapping="accountDetails" params="[acctNumber:'8675309']"> Show Account </g:link>
<a href="/details/8675309">Show Account</a>
<link:personList>List People</link:personList>
<a href="/showPeople">List People</a>
<link:accountDetails acctNumber="8675309">Show Account</link:accountDetails>
<a href="/details/8675309">Show Account</a>
href
, specify a Map
value to the attrs
attribute. These attributes will be applied directly to the href, not passed through to be used as request parameters.href
に適用されてしまうので、他の属性は、attrs
属性にMap
で指定します。<link:accountDetails attrs="[class: 'fancy']" acctNumber="8675309"> Show Account </link:accountDetails>
<a href="/details/8675309" class="fancy">Show Account</a>
7.4.12 URLフォーマットのカスタマイズ
addNumbers
in a controller named MathHelperController
would be something like /mathHelper/addNumbers
. Grails allows for the customization of this pattern and provides an implementation which replaces the camel case convention with a hyphenated convention that would support URLs like /math-helper/add-numbers
. To enable hyphenated URLs assign a value of "hyphenated" to the grails.web.url.converter
property in grails-app/conf/Config.groovy
.
MathHelperController
のアクションaddNumbers
の場合、デフォルトURLでのURLは、/mathHelper/addNumbers
のようになります。Grailsでは、このパターンをカスタマイズして、/math-helper/add-numbers
のようなハイフン形式にキャメルケース形式を入れ換える実装が可能です。ハイフンURLを可能にするには、grails-app/conf/Config.groovy
のgrails.web.url.converter
を"hyphenated"にします。// grails-app/conf/Config.groovygrails.web.url.converter = 'hyphenated'
grails.web.UrlConverter.BEAN_NAME
. If Grails finds a bean in the context with that name, it will be used as the default converter and there is no need to assign a value to the grails.web.url.converter
config property.
grails.web.UrlConverter.BEAN_NAME
で追加します。Grailsがそのビーン名称をコンテキストに有ることを認識した場合、指定したクラスの実装がデフォルトとなります。この場合は、grails.web.url.converter
プロパティは必要有りません。
// src/groovy/com/myapplication/MyUrlConverterImpl.groovypackage com.myapplication
class MyUrlConverterImpl implements grails.web.UrlConverter {
String toUrlElement(String propertyOrClassName) { // return some representation of a property or class name that should be used in URLs… } }
// src/groovy/com/myapplication/MyUrlConverterImpl.groovypackage com.myapplication
class MyUrlConverterImpl implements grails.web.UrlConverter {
String toUrlElement(String propertyOrClassName) { // URLで描写するプロパティ名またはクラス名を返す } }
// grails-app/conf/spring/resources.groovybeans = { "${grails.web.UrlConverter.BEAN_NAME}"(com.myapplication.MyUrlConverterImpl) }
7.4.13 コントローラネームスペース
If an application defines multiple controllers with the same name in different packages, the controllers must be defined in a namespace. The way to define a namespace for a controller is to define a static property namednamespace
in the controller and
assign a String to the property that represents the namespace.// grails-app/controllers/com/app/reporting/AdminController.groovy package com.app.reportingclass AdminController {
static namespace = 'reports'
// … }
// grails-app/controllers/com/app/security/AdminController.groovy package com.app.securityclass AdminController {
static namespace = 'users'
// … }
When defining url mappings which should be associated with a namespaced
controller, the namespace
variable needs to be part of the URL mapping.
// grails-app/conf/UrlMappings.groovy class UrlMappings {static mappings = { '/userAdmin' { controller = 'admin' namespace = 'users' }
'/reportAdmin' { controller = 'admin' namespace = 'reports' }
"/$namespace/$controller/$action?"() } }
Reverse URL mappings also require that the namespace
be specified.
<g:link controller="admin" namespace="reports">Click For Report Admin</g:link> <g:link controller="admin" namespace="users">Click For User Admin</g:link>
When resolving a URL mapping (forward or reverse) to a namespaced controller,
a mapping will only match if the namespace
has been provided. If
the application provides several controllers with the same name in different
packages, at most 1 of them may be defined without a namespace
property. If
there are multiple controllers with the same name that do not define a
namespace
property, the framework will not know how to distinguish between
them for forward or reverse mapping resolutions.
It is allowed for an application to use a plugin which provides a controller
with the same name as a controller provided by the application and for neither
of the controllers to define a namespace
property as long as the
controllers are in separate packages. For example, an application
may include a controller named com.accounting.ReportingController
and the application may use a plugin which provides a controller
named com.humanresources.ReportingController
. The only issue
with that is the URL mapping for the controller provided by the
plugin needs to be explicit in specifying that the mapping applies
to the ReportingController
which is provided by the plugin.
See the following example.
static mappings = { "/accountingReports" { controller = "reporting" } "/humanResourceReports" { controller = "reporting" plugin = "humanResources" } }
With that mapping in place, a request to /accountingReports
will
be handled by the ReportingController
which is defined in the
application. A request to /humanResourceReports
will be handled
by the ReportingController
which is provided by the humanResources
plugin.
There could be any number of ReportingController
controllers provided
by any number of plugins but no plugin may provide more than one
ReportingController
even if they are defined in separate packages.
Assigning a value to the plugin
variable in the mapping is only
required if there are multiple controllers with the same name
available at runtime provided by the application and/or plugins.
If the humanResources
plugin provides a ReportingController
and
there is no other ReportingController
available at runtime, the
following mapping would work.
static mappings = { "/humanResourceReports" { controller = "reporting" } }
It is best practice to be explicit about the fact that the controller is being provided by a plugin.
7.5 Webフロー
概要 Overview
From Grails 1.2 onwards Webflow is no longer in Grails core, so you must install the Webflow plugin to use this feature.Grails 1.2からWebフローはGrailsのコア機能では無くなりました。この機能を使用するには、Webフロープラグインをインストールする必要があります.
フローの作成 Creating a Flow
Flow
. For example:Flow
で終わるアクションをGrailsの通常にコントローラに作成します。例として:class BookController {def index() { redirect(action: "shoppingCart") }
def shoppingCartFlow = { … } }
Flow
suffix. In other words the name of the action of the above flow is shoppingCart
. Flow
を省略します。言い換えれば上記のフローのアクション名はshoppingCart
となります。
7.5.1 開始と終了のステート
class BookController { … def shoppingCartFlow ={ showCart { on("checkout").to "enterPersonalDetails" on("continueShopping").to "displayCatalogue" } … displayCatalogue { redirect(controller: "catalogue", action: "show") } displayInvoice() } }
showCart
node is the start state of the flow. Since the showCart state doesn't define an action or redirect it is assumed be a view state that, by convention, refers to the view grails-app/views/book/shoppingCart/showCart.gsp
.showCart
ノードになります。showCartがアクションまたリダイレクトを定義していない場合は、慣習によりgrails-app/views/book/shoppingCart/showCart.gsp
を参照して表示するビューステートとして見なされます。
grails-app/views/book/shoppingCart
.grails-app/views/book/shoppingCart
shoppingCart
flow also has two possible end states. The first is displayCatalogue
which performs an external redirect to another controller and action, thus exiting the flow. The second is displayInvoice
which is an end state as it has no events at all and will simply render a view called grails-app/views/book/shoppingCart/displayInvoice.gsp
whilst ending the flow at the same time. shoppingCart
フローには、2つの終了ステートがあります。一つ目のステートは、外部のコントローラアクションにリダイレクトしてフローから出るdisplayCatalogue
。二つ目のステートは、何もイベント処理が無く単純に、ビューのgrails-app/views/book/shoppingCart/displayInvoice.gsp
を描写して同時にフローを終了する、displayInvoice
です。showCart
, and not from any other state.showCart
になります。
7.5.2 アクションステートとビューステート
ビューステート View states
action
or a redirect
. So for example this is a view state:action
またridairect
を定義していないものがビューステートです。この例がビューステートになります:enterPersonalDetails { on("submit").to "enterShipping" on("return").to "showCart" }
grails-app/views/book/shoppingCart/enterPersonalDetails.gsp
by default. Note that the enterPersonalDetails
state defines two events: submit
and return
. The view is responsible for triggering these events. Use the render
method to change the view to be rendered:
grails-app/views/book/shoppingCart/enterPersonalDetails.gsp
を使用します。enterPersonalDetails
ステートは、submit
とreturn
という2つのイベントを定義しています。これらのイベントはビューでトリガーする必要があります。render
メソッドを使用して描写するビューを変更することが可能です:enterPersonalDetails { render(view: "enterDetailsView") on("submit").to "enterShipping" on("return").to "showCart" }
grails-app/views/book/shoppingCart/enterDetailsView.gsp
. Start the view
parameter with a / to use a shared view:grails-app/views/book/shoppingCart/enterDetailsView.gsp
を参照するようになります。view
のパラメータに / を使用して共有ビューを使用できます:enterPersonalDetails { render(view: "/shared/enterDetailsView") on("submit").to "enterShipping" on("return").to "showCart" }
grails-app/views/shared/enterDetailsView.gsp
grails-app/views/shared/enterDetailsView.gsp
を参照するようになります。アクションステート Action States
action
method and passing it a block of code to be executed:action
メソッドに実行したいコードブロックを渡してアクションステートを実装します:listBooks { action { [bookList: Book.list()] } on("success").to "showCatalogue" on(Exception).to "handleError" }
success
event will be triggered. In this case since we return a Map, which is regarded as the "model" and is automatically placed in flow scope.success
イベントがトリガーされます。この例では、モデルと見なしたMapを返して自動的にflowスコープに配置されます。on(Exception).to "handleError"
handleError
in the case of an exception.handleError
というステートにフローが移行します。processPurchaseOrder { action { def a = flow.address def p = flow.person def pd = flow.paymentDetails def cartItems = flow.cartItems flow.clear()def o = new Order(person: p, shippingAddress: a, paymentDetails: pd) o.invoiceNumber = new Random().nextInt(9999999) for (item in cartItems) { o.addToItems item } o.save() [order: o] } on("error").to "confirmPurchase" on(Exception).to "confirmPurchase" on("success").to "displayInvoice" }
Order
object. It then returns the order as the model. The important thing to note here is the interaction with the request context and "flow scope".Order
オブジェクトを生成しています。そして生成後オーダーをモデルで返しています。ここで重要なのはリクエストコンテキストと"flowスコープ"のやりとりです。アクションのトランジション Transition Actions
enterPersonalDetails { on("submit") { log.trace "Going to enter shipping" }.to "enterShipping" on("return").to "showCart" }
submit
event that simply logs the transition. Transition states are very useful for data binding and validation, which is covered in a later section.submit
イベントにコードブロックを渡してトランジションのログを実行しています。この後に解説をする、データバインディングとバリデーションで、このトランジションステートが便利に活用できます。
7.5.3 フロー実行イベント
ビューステートからのイベントトリガー Triggering Events from a View State
checkout
event and a continueShopping
event:checkout
イベントとcontinueShopping
イベントです:def shoppingCartFlow = { showCart { on("checkout").to "enterPersonalDetails" on("continueShopping").to "displayCatalogue" } … }
showCart
event is a view state it will render the view grails-app/book/shoppingCart/showCart.gsp
. Within this view you need to have components that trigger flow execution. On a form this can be done use the submitButton tag:shopCart
イベントはビューステートなので、grails-app/book/shoppingCart/showCart.gsp
を描写します。このビューの中に、フロートリガーを実行するコンポーネントを持つ必要があります。 submitButtonタグを使用したフォームを使います:<g:form> <g:submitButton name="continueShopping" value="Continue Shopping" /> <g:submitButton name="checkout" value="Checkout" /> </g:form>
shoppingCart
flow. The name attribute of each submitButton tag signals which event will be triggered. If you don't have a form you can also trigger an event with the link tag as follows:shoppingCart
フローへ送信します。それぞれのsubmitButtonタグのname属性にトリガーするイベントを指定します。フォームを使用しない場合は、次のようにlinkタグをイベントトリガーに使用できます:<g:link event="checkout" />
Prior to 2.0.0, it was required to specify the controller and/or action in forms and links, which caused the url to change when entering a subflow state. When the controller and action are not specified, all url's are relative to the main flow execution url, which makes your flows reusable as subflows and prevents issues with the browser's back button.
アクションからのイベントトリガー Triggering Events from an Action
action
you invoke a method. For example there is the built in error()
and success()
methods. The example below triggers the error()
event on validation failure in a transition action:error()
とsuccess()
メソッド(トランジションアクション)が有るのでそれを例に使用します。次の例では、トランジションアクションでバリデーションが失敗したらerror()
イベントをトリガーしています:enterPersonalDetails { on("submit") { def p = new Person(params) flow.person = p if (!p.validate()) return error() }.to "enterShipping" on("return").to "showCart" }
enterPersonalDetails
state.enterPersonalDetails
ステートに戻ります。shippingNeeded { action { if (params.shippingRequired) yes() else no() } on("yes").to "enterShipping" on("no").to "enterPayment" }
7.5.4 フローのスコープ
スコープの基礎 Scope Basics
flow
to store objects within "flow scope". Grails flows have five different scopes you can utilize:flow
という特殊なオブジェクトを使用しました。Grailsフローでは5つのスコープが利用できます:request
- Stores an object for the scope of the current requestflash
- Stores the object for the current and next request onlyflow
- Stores objects for the scope of the flow, removing them when the flow reaches an end stateconversation
- Stores objects for the scope of the conversation including the root flow and nested subflowssession
- Stores objects in the user's session
- request - 現在のリクエスト範囲のオブジェクトを蓄積
- flash - 現在と次までが有効範囲のオブジェクトを蓄積
- flow - フロー範囲で有効なオブジェクトを蓄積。フローが終了ステートに到達したら削除されます。
- conversation - ルートフローやサブフローを含む範囲での対話でのオブジェクトを蓄積
- session - ユーザのセッションへオブジェクトを蓄積
Grails service classes can be automatically scoped to a web flow scope. See the documentation on Services for more information.Grailsサービスクラスは自動的にflowスコープになります。詳しくはサービスを参照してください。
flow
scope as follows:flow
スコープにオブジェクトを配置できます:enterPersonalDetails { on("submit") { [person: new Person(params)] }.to "enterShipping" on("return").to "showCart" }
- Moves objects from flash scope to request scope upon transition between states;
- Merges objects from the flow and conversation scopes into the view model before rendering (so you shouldn't include a scope prefix when referencing these objects within a view, e.g. GSP pages).
- ステート間のトランジションでオブジェクトをflashスコープからrequestスコープへ移動します。
- 描写する前に、flowとconversationスコープのオブジェクトをビューモデルにマージします。(GSPページ等のビューでオブジェクトを参照する際はスコープ接頭辞を含まないでください)
フロースコープとシリアライズ Flow Scopes and Serialization
flash
, flow
or conversation
scope they must implement java.io.Serializable
or an exception will be thrown. This has an impact on domain classes in that domain classes are typically placed within a scope so that they can be rendered in a view. For example consider the following domain class:flash
、flow
またconversation
スコープに配置するオブジェクトは、必ずjava.io.Serializable
を継承してください。継承しない場合は例外を投げます。ドメインクラスは通常スコープに配置されビューの描写に使用するので影響を受けます。例として次のようなドメインクラスがあるとします:class Book {
String title
}
Book
class in a flow scope you will need to modify it as follows:Book
のインスタンスをflowスコープで使用する場合は次のようにします:class Book implements Serializable { String title }
class Book implements Serializable { String title Author author }
Author
association is not Serializable
you will also get an error. This also impacts closures used in GORM events such as onLoad
, onSave
and so on. The following domain class will cause an error if an instance is placed in a flow scope:Author
がSerializable
では無い場合はエラーとなります。onLoad
やonSave
等のGORMイベントのクロージャにも影響があります。次のドメインクラスをflowスコープに配置するとエラーとなります:class Book implements Serializable {String title
def onLoad = { println "I'm loading" } }
onLoad
event cannot be serialized. To get around this you should declare all events as transient
:onLoad
イベントのブロックがシリアライズされないからです。これを対応させるにはイベントにtransient
を指定します:class Book implements Serializable {String title
transient onLoad = { println "I'm loading" } }
class Book implements Serializable {String title
def onLoad() { println "I'm loading" } }
The flow scope contains a reference to the Hibernate session. As a result, any object loaded into the session through a GORM query will also be in the flow and will need to implement Serializable.If you don't want your domain class to be Serializable or stored in the flow, then you will need to evict the entity manually before the end of the state:
flow.persistenceContext.evict(it)
7.5.5 データバインディングとバリデーション
enterPersonalDetails
state. This state renders a view and waits for the user to enter the required information:enterPersonalDetails
ステートへトリガーしています。このステートはビューを描写して、ユーザが必要な情報をエントリーするのを待ちます:enterPersonalDetails { on("submit").to "enterShipping" on("return").to "showCart" }
<g:form> <!-- Other fields --> <g:submitButton name="submit" value="Continue"></g:submitButton> <g:submitButton name="return" value="Back"></g:submitButton> </g:form>
enterPersonalDetails { on("submit") { flow.person = new Person(params) !flow.person.validate() ? error() : success() }.to "enterShipping" on("return").to "showCart" }
Person
instance within flow
scope. Also interesting is that we perform validation and invoke the error()
method if validation fails. This signals to the flow that the transition should halt and return to the enterPersonalDetails
view so valid entries can be entered by the user, otherwise the transition should continue and go to the enterShipping
state.flow
スコープのPerson
インスタンスに渡している部分を注目してください。さらに興味深い部分は、バリデーションを実行して、失敗したらerror()
メソッドを実行している部分です。この結果を受け取るとトランジションは停止されenterPersonalDetails
ビューへ戻りユーザが正当な内容をエントリできるようになります。あるいは内容が問題無ければ、トランジションが続行されてenterShipping
ステートへ移動します。enterPersonalDetails { on("submit") { PersonDetailsCommand cmd -> flow.personDetails = cmd !flow.personDetails.validate() ? error() : success() }.to "enterShipping" on("return").to "showCart" }
7.5.6 サブフローとの対話
def searchFlow = { displaySearchForm { on("submit").to "executeSearch" } executeSearch { action { [results:searchService.executeSearch(params.q)] } on("success").to "displayResults" on("error").to "displaySearchForm" } displayResults { on("searchDeeper").to "extendedSearch" on("searchAgain").to "displaySearchForm" } extendedSearch { // Extended search subflow subflow(controller: "searchExtensions", action: "extendedSearch") on("moreResults").to "displayMoreResults" on("noResults").to "displayNoMoreResults" } displayMoreResults() displayNoMoreResults() }
extendedSearch
state. The controller parameter is optional if the subflow is defined in the same controller as the calling flow.extendedSearch
ステートで中にサブフローを参照しています。サブフローが同じコントローラで呼ばれる場合は、コントローラを指定するパラメータは省略可能です。Prior to 1.3.5, the previous subflow call would look like1.3.5以前、サブフローは、subflow(new SearchExtensionsController().extendedSearchFlow)
, with the requirement that the name of the subflow state be the same as the called subflow (minusFlow
). This way of calling a subflow is deprecated and only supported for backward compatibility.subflow(new SearchExtensionsController().extendedSearchFlow)
のように呼び出しました。この方法は非推奨となっており、下位互換のために対応はしています。
def extendedSearchFlow = { startExtendedSearch { on("findMore").to "searchMore" on("searchAgain").to "noResults" } searchMore { action { def results = searchService.deepSearch(ctx.conversation.query) if (!results) return error() conversation.extendedResults = results } on("success").to "moreResults" on("error").to "noResults" } moreResults() noResults() }
Notice how it places the extendedResults
in conversation scope. This scope differs to flow scope as it lets you share state that spans the whole conversation, i.e. a flow execution including all subflows, not just the flow itself. Also notice that the end state (either moreResults
or noResults
of the subflow triggers the events in the main flow:
extendedSearch { // Extended search subflow subflow(controller: "searchExtensions", action: "extendedSearch") on("moreResults").to "displayMoreResults" on("noResults").to "displayNoMoreResults" }
Subflow input and output
Using conversation scope for passing input and output between flows can be compared with using global variables to pass information between methods. While this is OK in certain situations, it is usually better to use method arguments and return values. In webflow speak, this means defining input and output arguments for flows.Consider following flow for searching a person with a certain expertise:
def searchFlow = { input { expertise(required: true) title("Search person") }search { onEntry { [personInstanceList: Person.findAllByExpertise(flow.expertise)] } on("select") { flow.person = Person.get(params.id) }.to("selected") on("cancel").to("cancel") }
selected { output { person {flow.person} } } cancel() }
}
This flow accepts two input parameters:
- a required expertise argument
- an optional title argument with a default value
All input arguments are stored in flow scope and are, just like local variables, only visible within this flow.
A flow that contains required input will throw an exception when an execution is started without providing the input. The consequence is that these flows can only be started as subflows.
Notice how an end state can define one or more named output values. If the value is a closure, this closure will be evaluated at the end of each flow execution. If the value is not a closure, the value will be a constant that is only calculated once at flow definition time.
When a subflow is called, we can provide it a map with input values:
def newProjectWizardFlow = { ...managerSearch { subflow(controller: "person", action: "search", input: [expertise : "management", title: "Search project manager"]) on("selected") { flow.projectInstance.manager = currentEvent.attributes.person }.to "techleadSearch" }
techleadSearch { subflow(controller: "person", action: "search", input: [expertise : { flow.technology }, title: "Search technical lead"]) on("selected") { flow.projectInstance.techlead = currentEvent.attributes.person }.to "projectDetails" }
...
}
Notice again the difference between constant values like expertise : "management"
and dynamic values like expertise : { flow.technology }
The subflow output is available via currentEvent.attributes
7.6 フィルタ
7.6.1 フィルタの適用
Filters
in the grails-app/conf
directory. Within this class define a code block called filters
that contains the filter definitions:grails-app/conf
ディレクトリに、名称がFilters
で終わるクラスを作成します。このクラスにはフィルタを記述する、filters
というコードブロックを定義します:class ExampleFilters { def filters = { // your filters here } }
filters
block has a name and a scope. The name is the method name and the scope is defined using named arguments. For example to define a filter that applies to all controllers and all actions you can use wildcards:filters
ブロックに定義する各フィルタには、範囲と名称を持たせます。名称はメソッド名で、範囲は引数で定義します。例として、ワイルドカードを使用した全コントローラ・アクションに適用されるフィルタを定義します:sampleFilter(controller:'*', action:'*') { // interceptor definitions }
- A controller and/or action name pairing with optional wildcards
- A URI, with Ant path matching syntax
- 省略可能なワイルドカードを使用した、コントローラ名と(または)アクション名の組み合わせ。
- Antパスマッチング書式のURI指定。
controller
- controller matching pattern, by default * is replaced with .* and a regex is compiledcontrollerExclude
- controller exclusion pattern, by default * is replaced with .* and a regex is compiledaction
- action matching pattern, by default * is replaced with .* and a regex is compiledactionExclude
- action exclusion pattern, by default * is replaced with .* and a regex is compiledregex
(true
/false
) - use regex syntax (don't replace '*' with '.*')uri
- a uri to match, expressed with as Ant style path (e.g. /book/**)uriExclude
- a uri pattern to exclude, expressed with as Ant style path (e.g. /book/**)find
(true
/false
) - rule matches with partial match (seejava.util.regex.Matcher.find()
)invert
(true
/false
) - invert the rule (NOT rule)
controller
- コントローラ対象のパターンマッチング, 既存値 * で正規表現コンパイル時には .*に置換されます。controllerExclude
- 除外するコントローラのパターン, 既存値 * で正規表現コンパイル時には .*に置換されます。action
- アクション対象のパターンマッチング, 既存値 * で正規表現コンパイル時には .*に置換されます。actionExclude
- 除外するアクションのパターン, 既存値 * で正規表現コンパイル時には .*に置換されます。regex (true/false)
- 正規表現の使用 ('*' を '.*'に置換しない指定)uri
- URIマッチ, Antスタイルパス (e.g. /book/**)uriExclude
- 除外するURIパターン, Antスタイルパス (e.g. /book/**)find (true/false)
- 部分マッチ (java.util.regex.Matcher.find()
を参照)invert (true/false)
- ルールを逆にする
- All controllers and actions
- 全てのコントローラとアクション
all(controller: '*', action: '*') {}
- Only for the
BookController
BookController
のみ
justBook(controller: 'book', action: '*') {}
- All controllers except the
BookController
BookController
以外の全て
notBook(controller: 'book', invert: true) {}
- All actions containing 'save' in the action name
- 'save'を名称に含めたアクション全て
saveInActionName(action: '*save*', find: true) {}
- All actions starting with the letter 'b' except for actions beginning with the phrase 'bad*'
- "bad*"を除外した、'b'で始まるアクション全て
actionBeginningWithBButNotBad(action: 'b*', actionExclude: 'bad*', find: true) {}
- Applied to a URI space
- URIへ適用
someURIs(uri: '/book/**') {}
- Applied to all URIs
- 全てのURIに適用
allURIs(uri: '/**') {}
filters
code block dictates the order in which they are executed. To control the order of execution between Filters
classes, you can use the dependsOn
property discussed in filter dependencies section.filters
コードブロックに定義した順序でフィルタは実行されます。Filters
クラス間での実行を制御するには、フィルタ依存セクションで記載されているdependsOn
プロパティが使用できます。Note: When exclude patterns are used they take precedence over the matching patterns. For example, if action is 'b*' and actionExclude is 'bad*' then actions like 'best' and 'bien' will have that filter applied but actions like 'bad' and 'badlands' will not.注意:除外パターンが使用された場合は除外マッチングパターンが優先されます。例として、アクションが'b*' で、actionExcludeが'bad*'の場合、'best','bien'等のアクションは適用されますが、'bad','badland'は適用されません。
7.6.2 フィルタの種類
before
- Executed before the action. Returnfalse
to indicate that the response has been handled that that all future filters and the action should not executeafter
- Executed after an action. Takes a first argument as the view model to allow modification of the model before rendering the viewafterView
- Executed after view rendering. Takes an Exception as an argument which will be non-null
if an exception occurs during processing. Note: this Closure is called before the layout is applied.
before
- アクションの前に実行されます。false
を返す事で、その後に実行されるフィルタとアクションは実行されません。after
- アクションの後に実行されます。最初の引数に、ビュー描写前に変更可能なビューモデルが渡されます。afterView
- ビュー描写後に実行されます。実行時に例外が発生した場合はnon-null
なExceptionが引数として渡されます。このクロージャはレイアウトが適用される前に実行されます。
class SecurityFilters { def filters = { loginCheck(controller: '*', action: '*') { before = { if (!session.user && !actionName.equals('login')) { redirect(action: 'login') return false } } } } }
loginCheck
filter uses a before
interceptor to execute a block of code that checks if a user is in the session and if not redirects to the login action. Note how returning false ensure that the action itself is not executed.loginCheck
フィルタは、before
インターセプタのコードブロックを実行することで、ユーザがセッションに無い場合はloginアクションへリダイレクトしています。falseを返す事で対象のアクション自身が実行されないようにします。Here's a more involved example that demonstrates all three filter types:
import java.util.concurrent.atomic.AtomicLongclass LoggingFilters {
private static final AtomicLong REQUEST_NUMBER_COUNTER = new AtomicLong() private static final String START_TIME_ATTRIBUTE = 'Controller__START_TIME__' private static final String REQUEST_NUMBER_ATTRIBUTE = 'Controller__REQUEST_NUMBER__'
def filters = {
logFilter(controller: '*', action: '*') {
before = { if (!log.debugEnabled) return true
long start = System.currentTimeMillis() long currentRequestNumber = REQUEST_NUMBER_COUNTER.incrementAndGet()
request[START_TIME_ATTRIBUTE] = start request[REQUEST_NUMBER_ATTRIBUTE] = currentRequestNumber
log.debug "preHandle request #$currentRequestNumber : " + "'$request.servletPath'/'$request.forwardURI', " + "from $request.remoteHost ($request.remoteAddr) " + " at ${new Date()}, Ajax: $request.xhr, controller: $controllerName, " + "action: $actionName, params: ${new TreeMap(params)}"
return true }
after = { Map model ->
if (!log.debugEnabled) return true
long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]
def msg = "postHandle request #$requestNumber: end ${new Date()}, " + "controller total time ${end - start}ms" if (log.traceEnabled) { log.trace msg + "; model: $model" } else { log.debug msg } }
afterView = { Exception e ->
if (!log.debugEnabled) return true
long start = request[START_TIME_ATTRIBUTE] long end = System.currentTimeMillis() long requestNumber = request[REQUEST_NUMBER_ATTRIBUTE]
def msg = "afterCompletion request #$requestNumber: " + "end ${new Date()}, total time ${end - start}ms" if (e) { log.debug "$msg \n\texception: $e.message", e } else { log.debug msg } } } } }
In this logging example we just log various request information, but note that the model
map in the after
filter is mutable. If you need to add or remove items from the model map you can do that in the after
filter.
7.6.3 変数とスコープ
- request - The HttpServletRequest object
- response - The HttpServletResponse object
- session - The HttpSession object
- servletContext - The ServletContext object
- flash - The flash object
- params - The request parameters object
- actionName - The action name that is being dispatched to
- controllerName - The controller name that is being dispatched to
- grailsApplication - The Grails application currently running
- applicationContext - The ApplicationContext object
- request - HttpServletRequestオブジェクト
- response - HttpServletResponseオブジェクト
- session - HttpSessionオブジェクト
- servletContext - ServletContextオブジェクト
- flash - flashオブジェクト
- params - リクエストパラメータオブジェクト
- actionName - ディスパッチ対象のアクション名
- controllerName - ディスパッチ対象のコントローラ名
- grailsApplication - 起動中のGrailsアプリケーション
- applicationContext - ApplicationContextオブジェクト
7.6.4 フィルタ依存関係
Filters
class, you can specify any other Filters
classes that should first be executed using the dependsOn
property. This is used when a Filters
class depends on the behavior of another Filters
class (e.g. setting up the environment, modifying the request/session, etc.) and is defined as an array of Filters
classes.Filters
クラスでは、dependsOn
プロパティに他のFilters
クラスを定義して先に実行させることが可能です。この機能はFilters
クラスが他のFilters
クラスの振る舞いに依存する場合等に使用します。Filters
classes:Filters
クラスの例を見ていきましょう:class MyFilters { def dependsOn = [MyOtherFilters]def filters = { checkAwesome(uri: "/*") { before = { if (request.isAwesome) { // do something awesome } } }
checkAwesome2(uri: "/*") { before = { if (request.isAwesome) { // do something else awesome } } } } }
class MyOtherFilters { def filters = { makeAwesome(uri: "/*") { before = { request.isAwesome = true } } doNothing(uri: "/*") { before = { // do nothing } } } }
dependsOn
MyOtherFilters. This will cause all the filters in MyOtherFilters whose scope matches the current request to be executed before those in MyFilters. For a request of "/test", which will match the scope of every filter in the example, the execution order would be as follows:dependsOn
にMyOtherFiltersを指定しています。これにより、リクエストがフィルタ範囲にマッチしたMyOtherFiltersのフィルタがMyFiltersの前に実行されます。この例では"/test"とリクエストした場合全てのフィルタにマッチします。フィルタ実行順は次のようになります:
- MyOtherFilters - makeAwesome
- MyOtherFilters - doNothing
- MyFilters - checkAwesome
- MyFilters - checkAwesome2
Filters
classes are enabled and the execution order of filters within each Filters
class are preserved.Filters
クラスの実行順が可能になり、各Filters
クラスのフィルタ実行順も保っています。org.codehaus.groovy.grails.plugins.web.filters.FiltersGrailsPlugin
) when debugging filter dependency issues.7.7 Ajax
Note: JavaScript examples use the jQuery library.
7.7.1 Ajax対応
<head>
tag of your page:<head>
タグ内に以下を追加します:<g:javascript library="jquery" />
jQuery
with any other library supplied by a plugin you have installed. This works because of Grails' support for adaptive tag libraries. Thanks to Grails' plugin system there is support for a number of different Ajax libraries including (but not limited to):jquery
の部分を入れ換えます。これはGrailsの適用性のあるタグライブラリで可能にしています。Grailsのプラグインには、次の様々なAjaxライブラリが存在します(これは一部です):
- jQuery
- Prototype
- Dojo
- YUI
- MooTools
7.7.1.1 リモートリンク
<g:remoteLink action="delete" id="1">Delete Book</g:remoteLink>
delete
action of the current controller with an id of 1
.delete
アクションに非同期リクエストを送信します。
7.7.1.2 コンテンツの更新
def delete() {
def b = Book.get(params.id)
b.delete()
render "Book ${b.id} was deleted"
}
<div id="message"></div> <g:remoteLink action="delete" id="1" update="message"> Delete Book </g:remoteLink>
message
div
to the response in this case "Book 1 was deleted"
. This is done by the update
attribute on the tag, which can also take a Map to indicate what should be updated on failure:messege
のidをもったdiv
エレメントの内容を"Book 1 was deleted"
に更新します。この更新動作は、タグのupdate
属性で対象を指定しています。さらにMapを使用して更新失敗時の表示先も指定できます:
<div id="message"></div> <div id="error"></div> <g:remoteLink update="[success: 'message', failure: 'error']" action="delete" id="1"> Delete Book </g:remoteLink>
error
div will be updated if the request failed.error
のIDをもつdivタグ内が更新されます。
7.7.1.3 リモートフォーム送信
<g:formRemote url="[controller: 'book', action: 'delete']" update="[success: 'message', failure: 'error']"> <input type="hidden" name="id" value="1" /> <input type="submit" value="Delete Book!" /> </g:formRemote >
<form action="delete"> <input type="hidden" name="id" value="1" /> <g:submitToRemote action="delete" update="[success: 'message', failure: 'error']" /> </form>
7.7.1.4 Ajaxイベント
<g:remoteLink action="show" id="1" update="success" onLoading="showProgress()" onComplete="hideProgress()">Show Book 1</g:remoteLink>
onSuccess
- The JavaScript function to call if successfulonFailure
- The JavaScript function to call if the call failedon_ERROR_CODE
- The JavaScript function to call to handle specified error codes (eg on404="alert('not found!')")onUninitialized
- The JavaScript function to call the a Ajax engine failed to initialiseonLoading
- The JavaScript function to call when the remote function is loading the responseonLoaded
- The JavaScript function to call when the remote function is completed loading the responseonComplete
- The JavaScript function to call when the remote function is complete, including any updates
onSuccess
- 成功した時に呼び出すJavaScriptファンクションonFailure
- 失敗した時に呼び出すJavaScriptファンクションon_ERROR_CODE
- 指定したエラーコードを取得したら呼び出すJavaScriptファンクション (eg on404="alert('not found!')")onUninitialized
- Ajaxエンジンの初期化に失敗したときに呼び出すJavaScriptファンクションonLoading
- レスポンスを読み込み中に呼び出すJavaScriptファンクションonLoaded
- レスポンスの読み込み完了時に呼び出すJavaScriptファンクションonComplete
- リモートファンクションが完全に完了(更新を含む)したら呼び出すJavaScriptファンクション
XMLHttpRequest
variable to obtain the request:XMLHttpRequest
変数で参照できます:<g:javascript> function fireMe(event) { alert("XmlHttpRequest = " + event) } } </g:javascript> <g:remoteLink action="example" update="success" onFailure="fireMe(XMLHttpRequest)">Ajax Link</g:remoteLink>
7.7.2 Prototypeを使用したAjax
runtime ":prototype:latest.release"
<g:javascript library="prototype" />
<g:javascript library="scriptaculous" />
7.7.3 Dojoを使用したAjax
compile ":dojo:latest.release"
<g:javascript library="dojo" />
7.7.4 GWTを使用したAjax
7.7.5 Ajax使用時のサーバサイド
- Content Centric Ajax - Where you just use the HTML result of a remote call to update the page
- Data Centric Ajax - Where you actually send an XML or JSON response from the server and programmatically update the page
- Script Centric Ajax - Where the server sends down a stream of JavaScript to be evaluated on the fly
- コンテント中心なAjax - リモートコールの結果HTMLページを更新する。
- データ中心なAjax - XMLまたはJSONをサーバから送信してページをプログラムで更新する。
- スクリプトを使用したAjax - サーバからJavascriptを送信して実行させる。
コンテント中心のAjax Content Centric Ajax
def showBook() { def b = Book.get(params.id)render(template: "bookTemplate", model: [book: b]) }
<g:remoteLink action="showBook" id="${book.id}" update="book${book.id}">Update Book</g:remoteLink><div id="book${book.id}"> <!--existing book mark-up --> </div>
JSONを使用したデータ中心のAjax Data Centric Ajax with JSON
import grails.converters.JSONdef showBook() { def b = Book.get(params.id)
render b as JSON }
<g:javascript> function updateBook(data) { $("#book" + data.id + "_title").html( data.title ); } </g:javascript> <g:remoteLink action="showBook" id="${book.id}" onSuccess="updateBook(data)"> Update Book </g:remoteLink> <g:set var="bookId">book${book.id}</g:set> <div id="${bookId}"> <div id="${bookId}_title">The Stand</div> </div>
XMLを使用したデータ中心のAjax Data Centric Ajax with XML
import grails.converters.XMLdef showBook() { def b = Book.get(params.id)
render b as XML }
<g:javascript> function updateBook(data) { var id = $(data).find("book").attr("id"); $("#book" + id + "_title").html( $(data).find("title").text() ); } <g:javascript> <g:remoteLink action="test" update="foo" onSuccess="updateBook(e)"> Update Book </g:remoteLink> <g:set var="bookId">book${book.id}</g:set> <div id="${bookId}"> <div id="${bookId}_title">The Stand</div> </div>
スクリプトを使用したAjax Script Centric Ajax with JavaScript
def showBook() { def b = Book.get(params.id)response.contentType = "text/javascript" String title = b.title.encodeAsJavaScript() render "$('#book${b.id}_title').html('${title}');" }
contentType
to text/javascript
. If you use Prototype on the client the returned JavaScript will automatically be evaluated due to this contentType
setting.contentType
をtext/javascript
にすることを思えて起きましょう。Prototypeを使用した場合はこのcontentType
で自動的にJavaScriptを評価します。AjaxとAjaxでない両方へのレスポンス Responding to both Ajax and non-Ajax requests
isXhr()
method to HttpServletRequest
which can be used to identify Ajax requests. For example you could render a page fragment using a template for Ajax requests or the full page for regular HTTP requests:HttpServletRequest
にisXhr()
メソッドを追加しています。例として、Ajaxリクエストの場合はテンプレートでページの一部を描写し、それ以外は全ページ描写を行う場合:def listBooks() { def books = Book.list(params) if (request.xhr) { render template: "bookTable", model: [books: books] } else { render view: "list", model: [books: books] } }
7.8 コンテントネゴシエーション
Accept
header, an explicit format request parameter or the extension of a mapped URI.Accept
ヘッダーまたはマップされたURIの拡張子でのコンテントネゴシエーションに対応しています。Mimeタイプの定義 Configuring Mime Types
grails-app/conf/Config.groovy
using the grails.mime.types
setting: grails-app/conf/Config.groovy
のgrails.mime.types
設定に幾つかのコンテントタイプがデフォルトで定義されています:grails.mime.types = [ all: '*/*', atom: 'application/atom+xml', css: 'text/css', csv: 'text/csv', form: 'application/x-www-form-urlencoded', html: ['text/html','application/xhtml+xml'], js: 'text/javascript', json: ['application/json', 'text/json'], multipartForm: 'multipart/form-data', rss: 'application/rss+xml', text: 'text/plain', hal: ['application/hal+json','application/hal+xml'], xml: ['text/xml', 'application/xml'] ]
Content Negotiation using the format parameter
Let's say a controller action can return a resource in a variety of formats: HTML, XML, and JSON. What format will the client get? The easiest and most reliable way for the client to control this is through a format
URL parameter.
So if you, as a browser or some other client, want a resource as XML, you can use a URL like this:
http://my.domain.org/books?format=xml
The result of this on the server side is a format
property on the response
object with the value xml
. You could code your controller action to return XML based on this property, but you can also make use of the controller-specific withFormat()
method:
import grails.converters.JSON import grails.converters.XMLclass BookController {
def list() { def books = Book.list()
withFormat { html bookList: books json { render books as JSON } xml { render books as XML } } } }
In this example, Grails will only execute the block inside withFormat()
that matches the requested content type. So if the preferred format is html
then Grails will execute the html()
call only. Each 'block' can either be a map model for the corresponding view (as we are doing for 'html' in the above example) or a closure. The closure can contain any standard action code, for example it can return a model or render content directly.
There is a special format, "all", that is handled differently from the explicit formats. If "all" is specified (normally this happens through the Accept header - see below), then the first block of withFormat()
is executed. You should not add an explicit "all" block. In the above example, a format of "all" will trigger the html
handler.
When using withFormat make sure it is the last call in your controller action as the return value of the withFormat
method is used by the action to dictate what happens next.
Acceptヘッダーを使用する Using the Accept header
Every incoming HTTP request has a special Accept header that defines what media types (or mime types) a client can "accept". In older browsers this is typically:
*/*
which simply means anything. However, newer browsers send more interesting values such as this one sent by Firefox:
text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5
This particular accept header is unhelpful because it indicates that XML is the preferred response format whereas the user is really expecting HTML. That's why Grails ignores the accept header by default for browsers. However, non-browser clients are typically more specific in their requirements and can send accept headers such as
application/json
As mentioned the default configuration in Grails is to ignore the accept header for browsers. This is done by the configuration setting grails.mime.disable.accept.header.userAgents
, which is configured to detect the major rendering engines and ignore their ACCEPT headers. This allows Grails' content negotiation to continue to work for non-browser clients:
grails.mime.disable.accept.header.userAgents = ['Gecko', 'WebKit', 'Presto', 'Trident']
For example, if it sees the accept header above ('application/json') it will set format
to json
as you'd expect. And of course this works with the withFormat()
method in just the same way as when the format
URL parameter is set (although the URL parameter takes precedence).
An accept header of '*/*' results in a value of all
for the format
property.
リクエストフォーマット vs レスポンスフォーマット Request format vs. Response format
CONTENT_TYPE
header and is typically used to detect if the incoming request can be parsed into XML or JSON, whilst the response format uses the file extension, format parameter or ACCEPT header to attempt to deliver an appropriate response to the client.CONTENT_TYPE
ヘッダの指定で入ってくるリクエストを認識してXMLまたJSON等に分類します。レスポンスフォーマットでは、ファイル拡張子、フォーマットパラメータまたはACCEPTヘッダを認識して適したレスポンスをクライアントに返します。withFormat
method available on the request:withFormat
メソッドを使用します:request.withFormat { xml { // read XML } json { // read JSON } }
リクエストパラメータformatでのコンテントネゴシエーション Content Negotiation with the format Request Parameter
format
request parameter:format
が使用できます:/book/list?format=xml
"/book/list"(controller:"book", action:"list") { format = "xml" }
URI拡張子でのコンテントネゴシエーション Content Negotiation with URI Extensions
/book/list.xml
This works as a result of the default URL Mapping definition which is:
"/$controller/$action?/$id?(.${format})?"{
Note the inclusion of the format
variable in the path. If you do not wish to use content negotiation via the file extension then simply remove this part of the URL mapping:
"/$controller/$action?/$id?"{
コンテントネゴシエーションをテスト Testing Content Negotiation
void testJavascriptOutput() { def controller = new TestController() controller.request.addHeader "Accept", "text/javascript, text/html, application/xml, text/xml, */*"controller.testAction() assertEquals "alert('hello')", controller.response.contentAsString }
void testJavascriptOutput() { def controller = new TestController() controller.params.format = 'js'controller.testAction() assertEquals "alert('hello')", controller.response.contentAsString }