8.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. |
A more advanced approach is to return an instance of the Spring ModelAndView class:
import org.springframework.web.servlet.ModelAndView
def 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 ])
}
One thing to bear in mind is that certain variable names can not be used in your model:
attributes
application
Currently, no error will be reported if you do use them, but this will hopefully change in a future version of Grails.
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 location grails-app/views/book/show.gsp
for this show
action:
class BookController {
def show() {
[book: Book.get(params.id)]
}
}
To render a different view, use the render method:
def show() {
def map = [book: Book.get(params.id)]
render(view: "display", model: map)
}
In this case Grails will attempt to render a view at the location 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 use an absolute path instead of a relative one:
def show() {
def map = [book: Book.get(params.id)]
render(view: "/shared/display", model: map)
}
In this case Grails will attempt to render a view at the location 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.
Selecting Views For Namespaced Controllers
If a controller defines a namespace for itself with the namespace property that will affect the root directory in which Grails will look for views which are specified with a relative path. The default root directory for views rendered by a namespaced controller is grails-app/views/<namespace name>/<controller name>/
. If the view is not found in the namespaced directory then Grails will fallback to looking for the view in the non-namespaced directory.
See the example below.
class ReportingController {
static namespace = 'business'
def humanResources() {
// This will render grails-app/views/business/reporting/humanResources.gsp
// if it exists.
// If grails-app/views/business/reporting/humanResources.gsp does not
// exist the fallback will be grails-app/views/reporting/humanResources.gsp.
// The namespaced GSP will take precedence over the non-namespaced GSP.
[numberOfEmployees: 9]
}
def accountsReceivable() {
// This will render grails-app/views/business/reporting/numberCrunch.gsp
// if it exists.
// If grails-app/views/business/reporting/numberCrunch.gsp does not
// exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.
// The namespaced GSP will take precedence over the non-namespaced GSP.
render view: 'numberCrunch', model: [numberOfEmployees: 13]
}
}
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 flexible render
method can be used:
render "Hello World!"
The above code writes the text "Hello World!" to the response. Other examples include:
// 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")
If you plan on using Groovy’s 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
}
This will actually call the form tag (which will return some text that will be ignored by the MarkupBuilder
). To correctly output a <form>
element, use the following:
def login() {
// ...
body {
h1 'Hello'
builder.form {
}
}
// ...
}