1. Introduction

The Grails Views project provides additional view technologies to the Grails framework, including JSON and Markup views.

JSON views allow rendering of JSON responses using Groovy’s StreamingJsonBuilder.

Markup views allow rendering of XML responses using Groovy’s MarkupTemplateEngine.

However the core of the project is extensible for potentially any kind of view implementation that is based on a Groovy DSL. The following sections cover usage of Groovy JSON views.

2. JSON Views

2.1. Introduction

JSON views are written in Groovy, end with the file extension gson and reside in the grails-app/views directory. They provide a DSL for producing output in the JSON format. A hello world example can be seen below:

grails-app/views/hello.gson
json.message {
    hello "world"
}

The above JSON view results in the output:

{"message":{ "hello":"world"}}

The json variable is an instance of StreamingJsonBuilder. See the documentation in the Groovy user guide for more information on StreamingJsonBuilder.

More example usages:

json(1,2,3) == "[1,2,3]"
json { name "Bob" } == '{"name":"Bob"}'
json([1,2,3]) { n it } == '[{"n":1},{"n":2},{"n":3}]'

You can specify a model to view in one of two ways. Either with a model block:

grails-app/views/hello.gson
model {
    String message
}
json.message {
    hello message
}

Or with the @Field transformation provided by Groovy:

grails-app/views/hello.gson
@Field String message
json.message {
    hello message
}
Although an import isn’t required to use @Field, the IDE may prompt you to do so.

2.2. Version History

The current release is 1.1.0.M2. Below is a version history.

1.1.0

  • Global template support

  • Template inheritance

  • Global templates for GORM for MongoDB / GeoJSON

  • Support for easily testing JSON views with JsonViewTest

  • Pagination support in HAL

  • Better Embedded support in HAL

  • Ability to access HTTP parameters and headers

  • Resource expansion with the expand parameter

  • Controller namespace support

  • Support for a default /object/_object.gson template

1.0.0

  • Initial 1.0.0 GA Release

2.3. Installation

To activate JSON views, add the following dependency to the dependencies block of your build.gradle:

compile "org.grails.plugins:views-json:1.1.0.M2"

If you are also using MongoDB you may want to add the views-json-templates dependency too which includes support for GeoJSON:

compile "org.grails.plugins:views-json-templates:1.1.0.M2"

To enable Gradle compilation of JSON views for production environment add the following to the buildscript dependencies block:

buildscript {
    ...
    dependencies {
        ...
        classpath "org.grails.plugins:views-gradle:1.1.0.M2"
    }
}

Then apply the org.grails.plugins.views-json Gradle plugin after any Grails core gradle plugins:

...
apply plugin: "org.grails.grails-web"
apply plugin: "org.grails.plugins.views-json"

This will add a compileGsonViews task to Gradle that is executed when producing a JAR or WAR file.

2.4. Templates

2.4.1. Template Basics

You can define templates starting with underscore _. For example given the following template called _person.gson:

grails-app/views/person/_person.gson
model {
    Person person
}
json {
    name person.name
    age person.age
}

You can render the template with the g.render method:

grails-app/views/person/show.gson
model {
    Person person
}
json g.render(template:"person", model:[person:person])

The above assumes the view is in the same directory as the template. If this is not the case you may need to use a relative URI to the template:

grails-app/views/family/show.gson
model {
    Person person
}
json g.render(template:"/person/person", model:[person:person])

2.4.2. Template Namespace

The previous example can be simplified using the template namespace:

grails-app/views/person/show.gson
model {
    Person person
}
json tmpl.person(person)

In this example, the name of the method call (person in this case) is used to dictate which template to render.

The argument to the template becomes the model. The name of the model variable is the same as the template name. If you wish to alter this you can pass a map instead:

grails-app/views/person/show.gson
model {
    Person person
}
json tmpl.person(individual:person)

In the above example the model variable passed to the _person.gson template is called individual.

This technique may also be used when you want to render a template using a relative path:

grails-app/views/person/show.gson
model {
    Person person
}
json tmpl.'/person/person'(person:person)

The template namespace even accepts a collection (or any Iterable object):

grails-app/views/person/show.gson
model {
    List<Person> people = []
}
json tmpl.person(people)

In this case the output is a JSON array. For example:

    [{"name":"Fred",age:10},{"name":"Bob",age:12}]

By passing in a collection the plugin will iterate over each element on the collection and render the template as a JSON array. If you do not want this to happen then use the variation of the method that takes a map instead:

grails-app/views/person/show.gson
model {
    List<Person> people = []
}
json tmpl.person(people:people)

2.4.3. More Ways to Render Templates

The g.render method is flexible, you can render templates in many forms:

model {
    Family family
}
json {
    name family.father.name
    age family.father.age
    oldestChild g.render(template:"person", model:[person: family.children.max { Person p -> p.age } ])
    children g.render(template:"person", collection: family.children, var:'person')
}

However, most of these use cases are more concise with the template namespace:

model {
    Family family
}
json {
    name family.father.name
    age family.father.age
    oldestChild tmpl.person( family.children.max { Person p -> p.age } ] )
    children tmpl.person( family.children )
}

2.4.4. Template Inheritance

JSON templates can inherit from a parent template. For example consider the following parent template:

grails-app/views/_parent.gson
model {
    Object object
}
json {
    hal.links(object)
    version "1.0"
}

A child template can inherit from the above template by using the inherits method:

grails-app/views/_person.gson
inherits template:"parent"
model {
    Person person
}
json {
    name person.name
}

The JSON from the parent and the child template will be combined so that the output is:

 {
   "_links": {
     "self": {
       "href": "http://localhost:8080/person/1",
       "hreflang": "en",
       "type": "application/hal+json"
     }
   },
   "version": "1.0",
   "name": "Fred"
 }

The parent template’s model will be formulated from the child templates model and the super class name. For example if the model is Person person where Person extends from Object then the final model passed to the parent template will look like:

[person:person, object:person]

If the Person class extended from a class called Mammal then the model passed to the parent would be:

[person:person, mammal:person]

This allows you to design your templates around object inheritance.

You can customize the model passed to the parent template using the model argument:

inherits template:"parent", model:[person:person]

2.5. Rendering Domain Classes

2.5.1. Basics of Domain Class Rendering

Typically your model may involve one or many domain instances. JSON views provide a render method for rendering these.

For example given the following domain class:

class Book {
    String title
}

And the following template:

model {
    Book book
}
json g.render(book)

The resulting output is:

{"id":1,"title":"The Stand"}

You can customize the rendering by including or excluding properties:

json g.render(book, [includes:['title']])

Or by providing a closure to provide additional JSON output:

json g.render(book) {
    pages 1000
}

Or combine the two approaches:

json g.render(book, [includes:['title']) {
    pages 1000
}

2.5.2. Deep Rendering of Domain Classes

Typically the g.render(..) method will only render objects one level deep. In other words if you have a domain class such as:

class Book {
    String title
    Author author
}

The resulting output will be something like:

{"id":1,"title":"The Stand","author":{id:1}}

If you wish for the author to be included as part of the rendering, there are two requirements, first you must make sure the association is initialized.

If the render method encounters a proxy, it will not traverse into the relationship to avoid N+1 query performance problems.

The same applies to one-to-many collection associations. If the association has not been initialized the render method will not traverse through the collection!

So you must make sure your query uses a join:

Book.findByTitle("The Stand", [fetch:[author:"join"]])

Secondly when calling the render method you should pass the deep argument:

json g.render(book, [deep:true])

Alternatively, to only expand a single association you can use the expand argument:

json g.render(book, [expand:['author']])
request parameters can also be used to expand associations (eg. ?expand=author), if you do not want to allow this, then use includes or excludes to include only the properties you want.

Finally, if you prefer to handle the rendering yourself you can do by excluding the property:

json g.render(book, [excludes:['author']) {
    author {
        name book.author.name
    }
}

2.5.3. Domain Class Rendering and Templates

An alternative to the default behaviour of the render method is to rely on templates.

In other words if you create a /author/_author.gson template and then use the g.render method on an instance of book:

json g.render(book)

Whenever the author association is encountered the g.render method will automatically render the /author/_author.gson template instead.

2.6. JSON View API

All JSON views subclass the JsonViewTemplate class by default.

The JsonViewTemplate superclass implements the JsonView trait which in turn extends the the GrailsView trait.

Thanks to these traits several methods and properties are available to JSON views that can be used to accomplish different tasks.

Links can be generated using the g.link(..) method:

json.person {
    name "bob"
    homepage g.link(controller:"person", id:"bob")
}

The g.link method is similar to the equivalent tag in GSP and allows you to easily create links to other controllers.

2.6.2. Altering the Response Headers

To customize content types and headers use the response object defined by the HttpView trait:

response.contentType "application/hal+json"
response.header "Token", "foo"
json.person {
    name "bob"
}

The HttpView trait defines a variety of methods for inspecting the request and altering the response.

The methods available are only a subset of the methods available via the HttpServletRequest and HttpServletResponse objects, this is by design as view logic should be limited and logic performed in the controller instead.

2.6.3. Accessing the Request

Various aspects of the request can be accessed by the request object defined by the HttpView trait:

json.person {
 name "bob"
 userAgent request.getHeader('User-Agent')
}

Parameters can be accessed via the params object which is an instance of Parameters:

json.person {
 name "bob"
 first params.int('offset', 0)
 sort params.get('sort', 'name')
}

2.6.4. Default Static Imports

The following classes' static properties are imported by default:

  • org.springframework.http.HttpStatus

  • org.springframework.http.HttpMethod

  • grails.web.http.HttpHeaders

This means that you can use the response object to set the status using these constants, instead of hard coded numbers:

response.status NOT_FOUND

Or generate links using the appropriate HTTP method:

g.link(resource:"books", method:POST)

2.6.5. I18n & Locale Integration

You can lookup i18n messages use the g.message method:

json.error {
    description g.message(code:'default.error.message')
}

You can also create locale specific views by appending the locale to view name. For example person_de.gson for German or person.gson for the default.

For more complex message lookups the messageSource property is an instance of the Spring MessageSource class.

2.7. Content Negotiation

GSON views integrate with Grails' content negotiation infrastructure. For example if you create two views called grails-app/views/book/show/show.gsp (for HTML) and grails-app/views/book/show/show.gson (for JSON), you can then define the following action:

grails-app/controllers/myapp/BookController.groovy
def show() {
    respond Book.get(params.id)
}

The result is that if you send a request to /book/show it will render show.gsp but if you send a request to /book/show.json it will render show.gson.

In addition, if the client sends a request with the Accept header containing application/json the show.gson view will be rendered.

2.7.1. Content Negotiation and Domain Classes

Content negotiation also works nicely with domain classes, for example if you want to define a template to render any instance of the Book domain class you can create a gson file that matches the class name.

For example given a class called demo.Book you can create grails-app/views/book/_book.gson and whenever respond is called with an instance of Book Grails will render _book.gson.

def show() {
    respond Book.get(params.id)
}

You can also define a /object/_object template that is rendered by default if no other template is found during content negotiation. To do this create a file called /grails-app/views/object/_object.gson where the name of the model is object, for example:

model {
    Object object
}
json g.render(object)

2.7.2. Content Negotiation and Versioned APIs

A typical use case when building REST APIs is the requirement to support different versions of the API. GSON views can be versioned by including the version in the name of the view.

Grails will then use the ACCEPT_VERSION header when resolving the view.

For example given a view called /book/show.gson if you wish to deprecate your previous API and create a version 2.0 API, you can rename the previous view /book/show_v1.0.gson and create a new /book/show.gson representing the new version of the API.

Then when the client sends a request with the ACCEPT_VERSION header containing v1.0 the /book/show_v1.0.gson view will be rendered instead of /book/show.gson.

2.7.3. Content Negotiation and View Resolving Strategy

Grails takes into account a number of factors when attempting to resolve the view including the content type, version and locale.

The following paths are searched:

  • view_name[_LOCALE][_ACCEPT_CONTENT_TYPE][_ACCEPT_VERSION].gson (Example: show_de_hal_v1.0.gson)

  • view_name[_LOCALE][_ACCEPT_CONTENT_TYPE].gson (Example: show_de_hal.gson)

  • view_name[_LOCALE][_ACCEPT_VERSION].gson (Example: show_de_v1.0.gson)

  • view_name[_LOCALE].gson (Example: show_de.gson)

  • view_name[_ACCEPT_CONTENT_TYPE][_ACCEPT_VERSION].gson (Example: show_hal_v1.0.gson)

  • view_name[_ACCEPT_VERSION][_ACCEPT_CONTENT_TYPE].gson (Example: show_v1.0_hal.gson)

  • view_name[_ACCEPT_CONTENT_TYPE].gson (Example: show_hal.gson)

  • view_name[_ACCEPT_VERSION].gson (Example: show_v1.0.gson)

  • view_name.gson (Example: show.gson)

The content type (defined by either the ACCEPT header or file extension in the URI) is taken into account to allow different formats for the same view.

2.7.4. Content Negotiation and Custom Mime Types

Some REST APIs use the notion of custom mime types to represent resources. Within Grails you can for example define custom mime types in grails-app/conf/application.yml:

grails:
    mime:
        types:
            all:      "*/*"
            book:     "application/vnd.books.org.book+json"
            bookList: "application/vnd.books.org.booklist+json"

Once these custom mime types have been defined you can then define a view such as show.book.gson for that particular mime type.

2.8. HAL Support

HAL is a standard format for representing JSON that has gained traction for its ability to represent links between resources and provide navigable APIs.

The JSON views plugin for Grails provides HAL support out-of-the-box. All JSON views implement the HalView trait, which provides an API for writing HAL views.

For example:

model {
    Book book
}
json {
    hal.links(book)
    hal.embedded {
        author( book.authors.first() ) { Author author ->
            name author.name
        }
    }
    title book.title
}
The call to hal.links() has to be the first element within the json{} closure.

This produces the HAL output:

{
    "_links": {
        "self": {
            "href": "http://localhost:8080/book/show/1",
            "hreflang": "en",
            "type": "application/hal+json"
        }
    },
    "_embedded": {
        "author": {
            "_links": {
                "self": {
                    "href": "http://localhost:8080/author/show/1",
                    "hreflang": "en",
                    "type": "application/hal+json"
                }
            },
            "name": "Stephen King"
        }
    },
    "title": "The Stand"
}

The above example uses the hal.links(..) method to render links for a domain resource and the hal.embedded(..) method to define any embedded objects that form part of the HAL response.

The hal.links(..) method will by default create a link to the resource, but you can define additional links by annotating the domain class with either grails.rest.Linkable or grails.rest.Resource and using the link method on the object:

book.link(rel:"publisher", href:"http://foo.com/books")

The link will then be included in the HAL output.

If you wish to be specific about which links to include you can do so by passing a map of objects to link to:

model {
    Book book
}
json {
    hal.links(self:book, author: book.author)
    ...
}

Alternatively, if you prefer to define the HAL links yourself then you can do so by passing a closure to the hal.links method:

model {
    Book book
}
json {
    hal.links {
        self {
            href '...'
            hreflang 'en'
            type "application/hal+json"
        }
    }
    ...
}

2.8.2. Rendering Domain Classes as HAL

If you prefer to let the plugin handle the rendering of your object you can use the hal.render(..) method:

model {
    Book book
}
json hal.render(book)

The hal.render method works the same as the g.render method, accepting the same arguments, the difference being it will output HAL links for the object via hal.links and also output associations fetched via a join query for hal.embedded.

For example you can also customize the content of the generated HAL with a closure:

model {
    Book book
}
json hal.render(book) {
    pages 1000
}

2.8.3. Embedded Association and HAL

Generally, when using the hal.render(..) method, _embedded associations are only rendered for associations that have been initialized and eagerly fetched. This means that the following query will not render the book.authors association:

Book.get(params.id)

However, this query will render the book.authors association:

Book.get(params.id, [fetch:[authors:'eager']])

This is by design and to prevent unexpected performance degradation due to N+1 query loading. If you wish to force the render method to render _embedded associations for HAL you can do see using the deep argument:

json hal.render(book, [deep:true])

You can prevent HAL _embedded associations from being rendering using the embedded:false parameter:

model {
    Book book
}
json hal.render(book, [embedded:false])

You can also render embedded associations without using the hal.render(..) method, by using the hal.embedded(..) method:

model {
    Book book
}
json {
    hal.embedded(book)
    title book.title
}
Like the hal.links(..) method, the hal.embedded(..) method should come first, before any other attributes, in your JSON output

You can also control which associations should be embedded by using a map argument instead:

model {
    Book book
}
json {
    hal.embedded(authors: book.authors)
    title book.title
}

And you can inline the contents of the book without any associations using the hal.inline(..) method:

model {
    Book book
}
json {
    hal.embedded(authors: book.authors)
    hal.inline(book)
}

To customize the contents of the inlined JSON output use a closure:

model {
    Book book
}
json {
    hal.embedded(authors: book.authors)
    hal.inline(book) {
        pages 300
    }
}
You cannot include additional content after the call to hal.inline(..) as this will produce invalid JSON

You can combine hal.embeddded(..) and hal.links(..) to obtain exactly the links and the embedded content you want:

model {
    Book book
}
json {
    hal.links(self: book )
    hal.embedded(authors: book.authors)
    hal.inline(book) {
        pages 300
    }
}

2.8.4. Specifying the HAL Content Type

The default HAL response content type is application/hal+json, however as discussed in the section on Custom Mime Type you can define your own response content types to represent your resources.

For example given the following configuration in grails-app/conf/application.yml:

grails:
    mime:
        types:
            all:      "*/*"
            book:     "application/vnd.books.org.book+json"

You can set the HAL content type to an explicit content type or one of the named content types defined in grails.mime.types in application.yml:

model {
    Book book
}
hal.type("book")
json {
    ...
}

2.8.5. HAL Pagination

The JSON views plugin for Grails provides navigable pagination support. Like the GSP <g:paginate> tag, the parameters include: total, max, offset, sort and order.

For example:

model {
    Iterable<Book> bookList
    Integer bookCount
    Integer max // optional, defaults to 10
    Integer offset // optional, defaults to 0
    String sort // optional
    String order // optional
}
json {
    hal.paginate(Book, bookCount, max, offset, sort, order)
    ...
}
Similar to hal.links() the hal.paginate() has to be the first element within the json{} closure.

When accessing http://localhost:8080/book?offset=10 this produces the navigable output like:

{
  "_links": {
    "self": {
      "href": "http://localhost:8080/book/index?offset=10&max=10",
      "hreflang": "en_US",
      "type": "application/hal+json"
    },
    "first": {
      "href": "http://localhost:8080/book/index?offset=0&max=10",
      "hreflang": "en_US"
    },
    "prev": {
      "href": "http://localhost:8080/book/index?offset=0&max=10",
      "hreflang": "en_US"
    },
    "next": {
      "href": "http://localhost:8080/book/index?offset=20&max=10",
      "hreflang": "en_US"
    },
    "last": {
      "href": "http://localhost:8080/book/index?offset=40&max=10",
      "hreflang": "en_US"
    }
  },
  ...
}
If there aren’t enough results to paginate the navigation links will not appear. Likewise the prev and next links are only present when there is a previous or next page.

If you have other links that you want to include along with the pagination links then you can use the hal.links(..) method with pagination arguments:

model {
    Author author
    Iterable<Book> bookList
    Integer bookCount
}
json {
    // the model, type to paginate, and the total count
    hal.links([author:author], Book, bookCount)
    ...
}

2.9. The JsonTemplateEngine

The JSON Views plugin registers a bean called jsonTemplateEngine of type JsonViewTemplateEngine.

This class is a regular Groovy TemplateEngine and you can use the engine to render JSON views outside the scope of an HTTP request.

For example:

@Autowired
JsonViewTemplateEngine templateEngine
void myMethod() {
        Template t = templateEngine.resolveTemplate('/book/show')
        def writable = t.make(book: new Book(title:"The Stand"))
        def sw = new StringWriter()
        writable.writeTo( sw )
        ...
}

2.10. Static Compilation

JSON views are statically compiled. You can disable static compilation if you prefer by setting grails.views.json.compileStatic:

grails:
    views:
        json:
            compileStatic: false
If you disable static compilation rendering performance will suffer.

For model variables you need to declare the types otherwise you will get a compilation error:

model {
    Person person
}
json {
    name person.name
    age person.age
}

2.11. Testing

Although generally testing can be done using functional tests via an HTTP client, the JSON views plugin also provides a trait which helps in writing either unit or integration tests.

To use the trait import the grails.plugin.json.view.test.JsonViewTest class and apply it to any Spock or JUnit test:

import grails.plugin.json.view.test.*
import spock.lang.Specification
class MySpec extends Specification implements JsonViewTest {
    ...
}

The trait provides a number of different render method implementations that can either render a JSON view found in grails-app/views or render an inline String. For example to render an inline template:

void "Test render a raw GSON view"() {
    when:"A gson view is rendered"
    JsonRenderResult result = render '''
        model {
            String person
        }
        json.person {
            name person
        }
''', [person:"bob"] (1)

    then:"The json is correct"
    result.json.person.name == 'bob' (2)
}
1 Use the render method to return a JsonRenderResult passing in a String that is the inline template and a map that is the model
2 Assert the parsed JSON, represented by the json property, is correct

To render an existing view or template use named arguments to specify an absolute path to either a template or a view:

when:"A gson view is rendered"
def result = render(template: "/path/to/template", model:[age:10])

then:"The json is correct"
result.json.name == 'Fred'
result.json.age == 10

If you are writing a unit test, and the model involves a domain class, you may need to add the domain class to the mappingContext object in order for it to be rendered correctly:

when:"A gson view is rendered for a domain class"
mappingContext.addPersistentEntity(MyDomain)
def result = render(template: "/path/to/template", model:[myDomain:MyDomain])

then:"The json is correct"
result.json.name == 'Fred'
result.json.age == 10

2.12. Plugin Support

Since JSON views are compiled all of a plugin’s views and templates are available for use in your applications.

The view resolution will look through all of the application’s configured plugins for views that match a particular name.

2.12.1. Customizing the Compile Task

Unless otherwise configured, the project name of the plugin (the gradle project.name) is used as the packageName when compiling JSON views.

In Gradle project.name is generally calculated from the current directory. That means that if there is mismatch between the current directory and your plugin name view resolution from a plugin could fail.

For example consider a plugin named FooBarGrailsPlugin, in this case Grails will search for views that match the plugin name foo-bar. However, if the directory where the plugin is located is called fooBar instead of foo-bar then view resolution will fail and the view will not be found when the plugin is installed into an application.

To resolve this issue you can customize the compileGsonViews task in the plugin’s build.gradle

compileGsonViews {
    packageName = "foo-bar"
}

By setting the packageName property to correctly match the convention of the plugin named (FooBarGrailsPlugin maps to foo-bar) view resolution will succeed.

2.12.2. Global Templates

You can create global templates that should apply to all types of a particular class (excluding primitive and simple types for performance reasons).

Below is an example Gradle build that adds a compileViews task for templates located into src/main/gson:

buildscript {
    repositories {
        mavenLocal()
        maven { url "https://repo.grails.org/grails/core" }
    }
    dependencies {
        classpath "org.grails.plugins:views-gradle:1.1.0.M2"
    }
}

import grails.views.gradle.json.*

apply plugin:"java"

repositories {
    mavenLocal()
    maven { url "https://repo.grails.org/grails/core" }
}

dependencies {
    compile "org.grails.plugins:views-json:1.1.0.M2"
    compileOnly "org.grails:grails-plugin-rest:3.1.7"
    compileOnly "javax.servlet:javax.servlet-api:3.0.1"
}

task( compileViews, type:JsonViewCompilerTask ) {
    source = project.file('src/main/gson')
    destinationDir = project.file('build/classes/main')
    packageName = ""
    classpath = configurations.compile + configurations.compileOnly
}
classes.dependsOn compileViews

Once this is in place if you want to render all instances of type foo.bar.Birthday create a template called src/main/gson/foo/bar/_birthday.gson then compile the template and place the JAR on the classpath of your application.

See the GeoJSON templates for MongoDB for example of global templates in action.

2.13. Configuration

JSON views configuration can be altered within grails-app/conf/application.yml. Any of the properties within the JsonViewConfiguration interface can be set. For example:

grails:
    views:
        json:
            compileStatic: true
            cache: true
            ...

Alternatively you can register a new JsonViewConfiguration bean using the bean name jsonViewConfiguration in grails-app/conf/resources.groovy.

The same settings in grails-app/conf/application.yml will also be used by the Gradle plugin for production compilation.

The Gradle plugin compiles views using a forked compiler. You can configure the forked compilation task in Gradle as follows:

compileGsonViews {
    compileOptions.forkOptions.memoryMaximumSize = '512mb'
}

See the API for GroovyForkOptions for more information.

2.13.1. Changing the view base class

All JSON views subclass the JsonViewTemplate class by default.

You can however change the subclass (which should be a subclass of JsonViewTemplate) using configuration:

grails:
    views:
        json:
            compileStatic: true
            baseTemplateClass: com.example.MyCustomJsonViewTemplate

2.13.2. Adding New Helper Methods via Traits

Alternatively, rather than modifying the base class, you can instead just add new methods via traits.

For example the HttpView uses the Enhances annotation to add the page object to all views:

import grails.artefact.Enhances
import grails.views.Views

@Enhances(Views.TYPE)
trait HttpView {

    /**
     * @return The response object
     */
    Response response
    ...
}

The result is all JSON views have a response object that can be used to control the HTTP response:

response.header "Token", "foo"
The trait cannot be defined in the same project as you are compilation as it needs to be on the classpath of the project you are compiling. You will need to create a Grails plugin and use a multi-project build in this scenario.

2.14. IntelliJ Support

When opening .gson files in Intellij they should be opened with the regular Groovy editor.

The plugin includes an IntelliJ GDSL file that provides code completion when using IntelliJ IDEA.

Intellij GDSL is a way to describe the available methods and properties so that code completion is available to the developer when opening .gson files.

The GDSL file is generally kept up-to-date with the codebase, however if any variation is spotted please raise an issue.

3. Markup Views

3.1. Introduction

Markup Views are written in Groovy, end with the file extension gml and reside in the grails-app/views directory.

The Markup Views plugin uses Groovy’s MarkupTemplateEngine and you can mostly use the Groovy user guide as a reference for the syntax.

Example Markup View:

model {
    Iterable<Map> cars
}
xmlDeclaration()
cars {
    cars.each {
        car(make: it.make, model: it.model)
    }
}

This produces the following output given a model such as [cars:[[make:"Audi", model:"A5"]]]:

<?xml version='1.0'?>
<cars><car make='Audi' model='A5'/></cars>

For further examples see Groovy’s MarkupTemplateEngine documentation.

All Markup views subclass the MarkupViewTemplate class by default.

The MarkupViewTemplate superclass implements the MarkupView trait which in turn extends the the GrailsView trait.

3.2. Installation

To activate Markup views, add the following dependency to the dependencies block of your build.gradle where VERSION is the version of the plugin you plan to use:

compile "org.grails.plugins:views-markup:VERSION"

To enable Gradle compilation of Markup views for production environment add the following to the buildscript dependencies block:

buildscript {
    ...
    dependencies {
        ...
        classpath "org.grails.plugins:views-gradle:VERSION"
    }
}

Then apply the org.grails.plugins.views-markup Gradle plugin after any Grails core gradle plugins:

...
apply plugin: "org.grails.grails-web"
apply plugin: "org.grails.plugins.views-markup"

This will add a compileMarkupViews task to Gradle that is executed when producing a JAR or WAR file.

3.3. Markup View API

All Markup views subclass the MarkupViewTemplate class by default.

The MarkupViewTemplate superclass implements the MarkupView trait which in turn extends the the GrailsView trait.

Much of the API is shared between JSON and Markup views. However, one difference compared to JSON views is that you must use this as a prefix when refering to properties from the parent class. For example to generate links this will produce a compilation error:

cars {
   cars.each {
       car(make: it.make, href: g.link(controller:'car'))
   }
}

However, the following works fine:

cars {
   cars.each {
       car(make: it.make, href: this.g.link(controller:'car'))
   }
}

Notice the this prefix when refering to this.g.link(..).

3.4. Configuration

Markup views configuration can be altered with grails-app/conf/application.yml. Any of the properties within the MarkupViewConfiguration class and Groovy’s TemplateConfiguration class can be set.

For example:

grails:
    views:
        markup:
            compileStatic: true
            cacheTemplates: true
            autoIndent: true
            ...

Alternatively you can register a new MarkupViewConfiguration bean using the bean name markupViewConfiguration in grails-app/conf/spring/resources.groovy.