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:

  1. def show() {
  2. [book: Book.get(params.id)]
  3. }
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:

  1. import org.springframework.web.servlet.ModelAndView
  2. def index() {
  3. // get some books just for the index page, perhaps your favorites
  4. def favoriteBooks = ...
  5. // forward to the list view to show them
  6. return new ModelAndView("/book/list", [ bookList : favoriteBooks ])
  7. }

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:

  1. class BookController {
  2. def show() {
  3. [book: Book.get(params.id)]
  4. }
  5. }

To render a different view, use the render method:

  1. def show() {
  2. def map = [book: Book.get(params.id)]
  3. render(view: "display", model: map)
  4. }

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:

  1. def show() {
  2. def map = [book: Book.get(params.id)]
  3. render(view: "/shared/display", model: map)
  4. }

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.

  1. class ReportingController {
  2. static namespace = 'business'
  3. def humanResources() {
  4. // This will render grails-app/views/business/reporting/humanResources.gsp
  5. // if it exists.
  6. // If grails-app/views/business/reporting/humanResources.gsp does not
  7. // exist the fallback will be grails-app/views/reporting/humanResources.gsp.
  8. // The namespaced GSP will take precedence over the non-namespaced GSP.
  9. [numberOfEmployees: 9]
  10. }
  11. def accountsReceivable() {
  12. // This will render grails-app/views/business/reporting/numberCrunch.gsp
  13. // if it exists.
  14. // If grails-app/views/business/reporting/numberCrunch.gsp does not
  15. // exist the fallback will be grails-app/views/reporting/numberCrunch.gsp.
  16. // The namespaced GSP will take precedence over the non-namespaced GSP.
  17. render view: 'numberCrunch', model: [numberOfEmployees: 13]
  18. }
  19. }

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:

  1. render "Hello World!"

The above code writes the text "Hello World!" to the response. Other examples include:

  1. // write some markup
  2. render {
  3. for (b in books) {
  4. div(id: b.id, b.title)
  5. }
  6. }
  1. // render a specific view
  2. render(view: 'show')
  1. // render a template for each item in a collection
  2. render(template: 'book_template', collection: Book.list())
  1. // render some text with encoding and content type
  2. 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:

  1. import groovy.xml.MarkupBuilder
  2. ...
  3. def login() {
  4. def writer = new StringWriter()
  5. def builder = new MarkupBuilder(writer)
  6. builder.html {
  7. head {
  8. title 'Log in'
  9. }
  10. body {
  11. h1 'Hello'
  12. form {
  13. }
  14. }
  15. }
  16. def html = writer.toString()
  17. render html
  18. }

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:

  1. def login() {
  2. // ...
  3. body {
  4. h1 'Hello'
  5. builder.form {
  6. }
  7. }
  8. // ...
  9. }