(Quick Reference)

6.1.3 模型和视图 - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: null

6.1.3 模型和视图

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.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.

返回模型

模型是在渲染的时候给视图用的一个映射(Map)。映射的键对应于视图中的命名变量。有很多方法都可以返回一个模型。首先,你可以通过明确返回映射(Map)实例的方式:

def show() {
    [book: Book.get(params.id)]
}

上述示例并 不会 影响到脚手架的视图-更多信息请参考脚手架章节

如果没有明确指定模型,那么控制器的属性将作为模型返回给视图,象如下代码所示那样:

class BookController {

List books List authors

def list() { books = Book.list() authors = Author.list() } }

这是可行的,因为控制器的缺省作用域是prototype。换句话说,每一个请求都将创建一个新的控制器。否则的话,上述的代码就不是线程安全的了,所有的用户将共享同样的数据。

在上述示例中,booksauthors属性在视图中将是有效的。

另外一个更高级的方式是返回一个Spring ModelAndView类的一个实例:

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 ]) }

只有一件事情需要担心,那就是在你的模型中可能某些变量名称是不可用的:

  • attributes
  • application

当前,如果你使用到了它们,也不会有任何错误提示报告出来,但是在将来的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 list 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 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)
}

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.

选择视图

在前面的两个示例中,我们并没有指定要用那个视图来渲染。那么Grails是如何知道那个将被选择?答案是规约依赖。在下面的示例中,Grails将自动的寻找位于grails-app/views/book/show.gsp的视图:

class BookController {
    def show() {
         [book: Book.get(params.id)]
    }
}

要渲染另外不同的视图,需要使用render方法:

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

在上述示例中,Grails将尝试使用视图grails-app/views/book/display.gsp来渲染。注意,Grails会根据grails-app/views下的book目录来自动地限定视图的位置。常规是这样的,但是要访问那些共享的视图,你还是需要使用绝对路径来替代相对路径:

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

在这个示例中,Grails将尝试使用视图grails-app/views/shared/display.gsp来渲染。

Grails也支持JSP的视图,因此如果一个预期的GSP没有找到,但是有相应的JSP,那么它将使用此JSP。

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 {
        }
    }
    // …
}

渲染响应

有时候在控制器中直接渲染文本或者代码片段到响应是很容易的(比如Ajax的应用)。这时,就可以使用灵活性很高的render方法了:

render "Hello World!"

上述代码输出"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")

如果你打算使用Groovy的MarkupBuilder来生成HTML来供render方法使用,这时,你要注意HTML元素和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 }

这将实际调用form标签(其返回的文本将被MarkupBuilder忽略)。要正确地输出<form>元素,需要使用如下的例子:

def login() {
    // …
    body {
        h1 'Hello'
        builder.form {
        }
    }
    // …
}