<< 6Object Relational Mapping (GORM)
(Quick Reference)
8Validation >>
7 The Web Layer - Reference Documentation
Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith
Version: 2.1.1
Table of Contents
7.1Controllers
7.1.1Understanding Controllers and Actions
7.1.2Controllers and Scopes
7.1.3Models and Views
7.1.4Redirects and Chaining
7.1.5Controller Interceptors
7.1.6Data Binding
7.1.7XML and JSON Responses
7.1.8More on JSONBuilder
7.1.9Uploading Files
7.1.10Command Objects
7.1.11Handling Duplicate Form Submissions
7.1.12Simple Type Converters
7.1.13Asynchronous Request Processing
7.2Groovy Server Pages
7.2.1GSP Basics
7.2.1.1Variables and Scopes
7.2.1.2Logic and Iteration
7.2.1.3Page Directives
7.2.1.4Expressions
7.2.2GSP Tags
7.2.2.1Variables and Scopes
7.2.2.2Logic and Iteration
7.2.2.3Search and Filtering
7.2.2.4Links and Resources
7.2.2.5Forms and Fields
7.2.2.6Tags as Method Calls
7.2.3Views and Templates
7.2.4Layouts with Sitemesh
7.2.5Static Resources
7.2.5.1Including resources using the resource tags
7.2.5.2Other resource tags
7.2.5.3Declaring resources
7.2.5.4Overriding plugin resources
7.2.5.5Optimizing your resources
7.2.5.6Debugging
7.2.5.7Preventing processing of resources
7.2.5.8Other Resources-aware plugins
7.2.6Sitemesh Content Blocks
7.2.7Making Changes to a Deployed Application
7.2.8GSP Debugging
7.3Tag Libraries
7.3.1Variables and Scopes
7.3.2Simple Tags
7.3.3Logical Tags
7.3.4Iterative Tags
7.3.5Tag Namespaces
7.3.6Using JSP Tag Libraries
7.3.7Tag return value
7.4URL Mappings
7.4.1Mapping to Controllers and Actions
7.4.2Embedded Variables
7.4.3Mapping to Views
7.4.4Mapping to Response Codes
7.4.5Mapping to HTTP methods
7.4.6Mapping Wildcards
7.4.7Automatic Link Re-Writing
7.4.8Applying Constraints
7.4.9Named URL Mappings
7.4.10Customizing URL Formats
7.5Web Flow
7.5.1Start and End States
7.5.2Action States and View States
7.5.3Flow Execution Events
7.5.4Flow Scopes
7.5.5Data Binding and Validation
7.5.6Subflows and Conversations
7.6Filters
7.6.1Applying Filters
7.6.2Filter Types
7.6.3Variables and Scopes
7.6.4Filter Dependencies
7.7Ajax
7.7.1Ajax Support
7.7.1.1Remoting Linking
7.7.1.2Updating Content
7.7.1.3Remote Form Submission
7.7.1.4Ajax Events
7.7.2Ajax with Prototype
7.7.3Ajax with Dojo
7.7.4Ajax with GWT
7.7.5Ajax on the Server
7.8Content Negotiation
7 The Web Layer
7.1 Controllers
A controller handles requests and creates or prepares the response. A controller can generate the response directly or delegate to a view. To create a controller, simply create a class whose name ends withController
in the grails-app/controllers
directory (in a subdirectory if it's in a package).The default URL Mapping configuration ensures that the first part of your controller name is mapped to a URI and each action defined within your controller maps to URIs within the controller name URI.
7.1.1 Understanding Controllers and Actions
Creating a controller
Controllers can be created with the create-controller or generate-controller command. For example try running the following command from the root of a Grails project:grails create-controller book
grails-app/controllers/myapp/BookController.groovy
:package myappclass BookController { def index() { }
}
BookController
by default maps to the /book URI (relative to your application root).Thecreate-controller
andgenerate-controller
commands are just for convenience and you can just as easily create controllers using your favorite text editor or IDE
Creating Actions
A controller can have multiple public action methods; each one maps to a URI:class BookController { def list() { // do controller logic // create model return model } }
/book/list
URI by default thanks to the property being named list
.Public Methods as Actions
In earlier versions of Grails actions were implemented with Closures. This is still supported, but the preferred approach is to use methods.Leveraging methods instead of Closure properties has some advantages:- 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.
grails.compile.artefacts.closures.convert
property to true in BuildConfig.groovy
:
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.
The Default Action
A controller has the concept of a default URI that maps to the root URI of the controller, for example/book
for BookController
. The action that is called when the default URI is requested is dictated by the following rules:
- 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:
static defaultAction = "list"
7.1.2 Controllers and Scopes
Available Scopes
Scopes are hash-like objects where you can store variables. The following scopes are available to controllers:- 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
Accessing Scopes
Scopes can be accessed using the variable names above in combination with Groovy's array index operator, even on classes provided by the Servlet API such as the HttpServletRequest: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 } }
Using Flash Scope
Grails supports the concept of flash scope as a temporary store to make attributes available for this request and the next request only. Afterwards the attributes are cleared. This is useful for setting a message directly before redirecting, for example: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.Note that the attribute name can be anything you want, and the values are often strings used to display messages, but can be any object type.Scoped Controllers
By default, a new controller instance is created for each request. In fact, because the controller isprototype
scoped, it is thread-safe since each request happens on its own thread.You can change this behaviour by placing a controller in a particular scope. The supported scopes are:
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)
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:grails.controllers.defaultScope = "singleton"
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
.
7.1.3 Models and Views
Returning the Model
A model is a Map that the view uses when rendering. The keys within that Map correspond to variable names accessible by the view. There are a couple of ways to return a model. First, you can explicitly return a Map instance: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.If no explicit model is returned the controller's properties will be used as the model, thus allowing you to write code like this:
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.In the above example the
books
and authors
properties will be available in the view.A more advanced approach is to return an instance of the Spring ModelAndView class: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
In both of the previous two examples there was no code that specified which view to render. So how does Grails know which one to pick? The answer lies in the conventions. Grails will look for a view at the locationgrails-app/views/book/show.gsp
for this list
action: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:def show() {
def map = [book: Book.get(params.id)]
render(view: "/shared/display", model: map)
}
grails-app/views/shared/display.gsp
.Grails also supports JSPs as views, so if a GSP isn't found in the expected location but a JSP is, it will be used instead.Rendering a Response
Sometimes it's easier (for example with Ajax applications) to render snippets of text or code to the response directly from the controller. For this, the highly flexiblerender
method can be used:render "Hello World!"
// write some markup
render {
for (b in books) {
div(id: b.id, b.title)
}
}
// render a specific view render(view: 'show')
// render a template for each item in a collection
render(template: 'book_template', collection: Book.list())
// render some text with encoding and content type 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: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:def login() { // … body { h1 'Hello' builder.form { } } // … }
7.1.4 Redirects and Chaining
Redirects
Actions can be redirected using the redirect controller method:class OverviewController { def login() {} def find() { if (!session.user) redirect(action: 'login') return } … } }
sendRedirect
method.The redirect
method expects one of:
- Another closure within the same controller class:
// Call the login action within the same class redirect(action: login)
- The name of an action (and controller name if the redirect isn't to an action in the current controller):
// Also redirects to the index action in the home controller redirect(controller: 'home', action: 'index')
- A URI for a resource relative the application context path:
// Redirect to an explicit URI
redirect(uri: "/login.html")
- Or a full URL:
// Redirect to a URL
redirect(url: "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:redirect(action: "next", params: params)
redirect(controller: "test", action: "show", fragment: "profile")
Chaining
Actions can also be chained. Chaining allows the model to be retained from one action to the next. For example calling thefirst
action in this action: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:chain(action: "action1", model: [one: 1], params: [myparam: "param1"])
7.1.5 Controller Interceptors
Often it is useful to intercept processing based on either request, session or application state. This can be achieved with action interceptors. There are currently two types of interceptors: before and after.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
Before Interception
ThebeforeInterceptor
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: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.After Interception
Use theafterInterceptor
property to define an interceptor that is executed after an action:def after