Colin Harrington
G3 Summit US 2016
Grails is a powerful web framework, for the Java platform aimed at multiplying developers’ productivity thanks to a Convention-over-Configuration, sensible defaults and opinionated APIs. It integrates smoothly with the JVM, allowing you to be immediately productive whilst providing powerful features, including integrated ORM, Domain-Specific Languages, runtime and compile-time meta-programming and Asynchronous programming.
spring-web
MVC
(Typically Domain/ GORM)
(Controller Layer)
(View Layer)
http://2.bp.blogspot.com/-o7yfxMokYs8/U4Xsxd9dcGI/AAAAAAAAAlQ/ySHAnAiqg0A/s1600/mvc+s.png
def show() {
response.sendError(418)
}
def show() {
[book: Book.get(params.id)]
}
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 ])
}
// render the view with the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(view: "viewName", model: [book: theShining])
// renders text to response
render "some text"
-----------------------------------------------------------------------
// renders text for a specified content-type/encoding
render(text: "<xml>some xml</xml>",
contentType: "text/xml", encoding: "UTF-8")
// render a template to the response for the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", model: [book: theShining])
-----------------------------------------------------------------------
// render each item in the collection using the specified template
render(template: "book", collection: [b1, b2, b3])
-----------------------------------------------------------------------
// render a template to the response for the specified bean
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(template: "book", bean: theShining)
// render the view with the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(view: "viewName", model: [book: theShining])
-----------------------------------------------------------------------
// render the view with the controller as the model
render(view: "viewName")
// render some markup to the response
render {
div(id: "myDiv", "some text inside the div")
}
-----------------------------------------------------------------------
// render some XML markup to the response
render(contentType: "text/xml") {
books {
for (b in books) {
book(title: b.title, author: b.author)
}
}
}
-----------------------------------------------------------------------
// render a JSON ( http://www.json.org ) response with the builder attribute:
render(contentType: "application/json") {
book(title: b.title, author: b.author)
}
// Automatic marshalling of XML and JSON
import grails.converters.*
...
render Book.list(params) as JSON
-----------------------------------------------------------------------
render Book.get(params.id) as XML
// render a file
render(file: new File(absolutePath), fileName: "book.pdf")
// render with status code
render(status: 503, text: 'Failed to update book ${b.id}')
<html>
<body>
<% out << "Hello GSP!" %>
</body>
</html>
-----------------------------------------------------------------------
<html>
<body>
<%="Hello GSP!" %>
</body>
</html>
-----------------------------------------------------------------------
<html>
<body>
<%-- This is my comment --%>
<%="Hello GSP!" %>
</body>
</html>
<html>
<body>
<% [1,2,3,4].each { num -> %>
<p><%="Hello ${num}!" %></p>
<%}%>
</body>
</html>
-----------------------------------------------------------------------
<html>
<body>
<% if (params.hello == 'true')%>
<%="Hello!"%>
<% else %>
<%="Goodbye!"%>
</body>
</html>
<%@ page import="java.awt.*" %>
<%@ page contentType="text/markdown" %>
<html>
<body>
Hello ${params.name}
</body>
</html>
<g:example />
-----------------------------------------------------------------------
<g:example>
Hello world
</g:example>
-----------------------------------------------------------------------
<g:example attr="${new Date()}">
Hello world
</g:example>
actionSubmit, actionSubmitImage, applyLayout, checkBox, collect, cookie, country, countrySelect, createLink, createLinkTo, currencySelect, datePicker, each, eachError, else, elseif, external, field, fieldError, fieldValue, findAll, form, formatBoolean, formatDate, formatNumber, grep, hasErrors, header, hiddenField, if, img, include, isAvailable, isNotAvailable, javascript, join, layoutBody, layoutHead, layoutTitle, link, localeSelect, message, meta, pageProperty, paginate, passwordField, radio, radioGroup, render, renderErrors, resource, select, set, sortableColumn, submitButton, textArea, textField, timeZoneSelect, unless, uploadForm, while
class SimpleTagLib {
/**
* Renders the body with an emoticon.
*
* @attr happy whether to show a happy emoticon ('true') or
* a sad emoticon ('false')
*/
def emoticon = { attrs, body ->
out << body() << (attrs.happy == 'true' ? " :-)" : " :-(")
}
}
-----------------------------------------------------------------------
<g:emoticon happy="true">Hi John</g:emoticon>
def isAdmin = { attrs, body ->
def user = attrs.user
if (user && checkUserPrivs(user)) {
out << body()
}
}
-----------------------------------------------------------------------
<g:isAdmin user="${myUser}">
// some restricted content
</g:isAdmin>
def repeat = { attrs, body ->
attrs.times?.toInteger()?.times { num ->
out << body(num)
}
}
-----------------------------------------------------------------------
<g:repeat times="3">
<p>Repeat this 3 times! Current repeat = ${it}</p>
</g:repeat>
package org.springframework.web.servlet;
import java.util.Locale;
/**
* Interface to be implemented by objects that can resolve views by name.
...
*/
public interface ViewResolver {
/**
* Resolve the given view by name.
...
*/
View resolveViewName(String viewName, Locale locale) throws Exception;
}
├── grails-app
│ └── views
│ ├── bar
│ ├── error.gsp
│ ├── index.gsp
│ ├── layouts
│ │ └── main.gsp
│ └── notFound.gsp
./grails-app/views
./grails-app/views
Found in grails-app/views Filename ending in .gsp
class BookController {
def show() {
[book: Book.get(params.id)]
}
}
grails-app/views/book/show.gsp
Resolves to:
def show() {
def map = [book: Book.get(params.id)]
render(view: "display", model: map)
}
grails-app/views/book/display.gsp
Resolves to:
def show() {
def map = [book: Book.get(params.id)]
render(view: "/shared/display", model: map)
}
grails-app/views/shared/display.gsp
Resolves to:
Grails uses the convention of placing an underscore before the name of a view to identify it as a template. For example, you might have a template that renders Books located at
./grails-app/views/book/_bookTemplate.gsp
def bookData() {
def b = Book.get(params.id)
render(template:"bookTemplate", model:[book:b])
}
def bookData() {
def b = Book.get(params.id)
String content = g.render(template:"bookTemplate", model:[book:b])
render content
}
Using the taglibs
<g:render template="bookTemplate" model="[book:myBook]" />
<tmpl:bookTemplate book="${myBook}" />
Template Namespace
<g:render template="/shared/mySharedTemplate" />
(Used to process Grails' Layouts)
http://wiki.sitemesh.org/wiki/display/sitemesh3/SiteMesh+3+Overview
grails-app/views/layouts
<html>
<head>
<title><g:layoutTitle default="An example decorator" /></title>
<g:layoutHead />
</head>
<body onload="${pageProperty(name:'body.onload')}">
<div class="menu"><!--my common menu goes here--></div>
<div class="body">
<g:layoutBody />
</div>
</body>
</html>
<html>
<head>
<title><g:layoutTitle default="An example decorator" /></title>
<g:layoutHead />
</head>
<body onload="${pageProperty(name:'body.onload')}">
<div class="menu"><!--my common menu goes here--></div>
<div class="body">
<g:layoutBody />
</div>
</body>
</html>
layoutTitle - outputs the target page’s title
layoutHead - outputs the target page’s head tag contents
layoutBody - outputs the target page’s body tag contents
grails-app/views/layouts/main.gsp
<html>
<head>
<title>An Example Page</title>
<meta name="layout" content="main" />
</head>
<body>This is my content!</body>
</html>
<html>
<head>
<title>An Example Page</title>
</head>
<body onload="">
<div class="menu"><!--my common menu goes here--></div>
<div class="body">
This is my content!
</div>
</body>
</html>
class BookController {
static layout = 'customer'
def list() { ... }
}
class BookController {
static layout = 'custom/customer'
def list() { ... }
}
class BookController {
def list() { ... }
}
grails-app/views/layouts/book/list.gsp
... or ...
grails-app/views/layouts/book.gsp
... or ...
grails-app/views/layouts/application.gsp
- configuration -
grails.sitemesh.default.layout = 'myLayoutName'
<content tag="navbar">
... draw the navbar here...
</content>
<div id="nav">
<g:applyLayout name="navLayout">
<g:pageProperty name="page.navbar" />
</g:applyLayout>
</div>
grails.gsp.enable.reload = true
grails.gsp.view.dir = "/var/www/grails/my-app/"
Accept header
format parameter (or url mapping)
/books.json
grails.mime.types
package example
class BookController {
def index() {
respond Book.list()
}
}
package example
class BookController {
static responseFormats = ['json', 'html']
def index() {
respond Book.list()
}
}
http://my.domain.org/books?format=xml
"/book/list"(controller:"book", action:"list") {
format = "xml"
}
import grails.converters.JSON
import grails.converters.XML
class BookController {
def list() {
def books = Book.list()
withFormat {
html bookList: books
json { render books as JSON }
xml { render books as XML }
'*' { render books as JSON }
}
}
}
/book/list.xml
"/$controller/$action?/$id?(.$format)?"{ ... }
matching config via 'grails.mime.types'
- JSON Views
- Markup Views
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.
json.message {
hello "world"
}
grails-app/views/hello.gson
{"message":{ "hello":"world"}}
model {
String message
}
json.message {
hello message
}
@Field String message
json.message {
hello message
}
or using @Field
model {
Person person
}
json {
name person.name
age person.age
}
grails-app/views/person/_person.gson
model {
Person person
}
json g.render(template:"person", model:[person:person])
model {
Person person
}
json tmpl.person(person)
Or using the tmpl namespace
model {
List<Person> people = []
}
json tmpl.person(people)
[{"name":"Fred",age:10},{"name":"Bob",age:12}]
outputs:
model {
Object object
}
json {
hal.links(object)
version "1.0"
}
inherits template:"parent"
model {
Person person
}
json {
name person.name
}
grails-app/views/_parent.gson
grails-app/views/_person.gson
Grails takes into account a number of factors when attempting to resolve the view including the content type, version and locale.
{Locale, Accept ContentType, Accept Version}
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)
model {
Book book
}
json {
hal.links(book)
hal.embedded {
author( book.authors.first() ) { Author author ->
name author.name
}
}
title book.title
}
Markup Views are written in Groovy, end with the file extension gml and reside in the grails-app/views directory.
model {
Iterable<Map> cars
}
xmlDeclaration()
cars {
cars.each {
car(make: it.make, model: it.model)
}
}
<?xml version='1.0'?>
<cars><car make='Audi' model='A5'/></cars>
What about the other uses?
<%@ page contentType="image/svg+xml;charset=UTF-8" %>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 105">
<g fill="#97C024" stroke="#97C024" stroke-linejoin="round" stroke-linecap="round">
<path d="M14,40v24M81,40v24M38,68v24M57,68v24M28,42v31h39v-31z" stroke-width="12"/>
<path d="M32,5l5,10M64,5l-6,10 " stroke-width="2"/>
</g>
<path d="M22,35h51v10h-51zM22,33c0-31,51-31,51,0" fill="#97C024"/>
<g fill="#FFF">
<circle cx="36" cy="22" r="2"/>
<circle cx="59" cy="22" r="2"/>
</g>
</svg>
<%@ page contentType="text/markdown" %>
# H1
## H2
### H3
#### H4
##### H5
###### H6
Alternatively, for H1 and H2, an underline-ish style:
Alt-H1
======
Alt-H2
------
static mappings = {
"/"(view: "/index") // map the root URL
}
static mappings = {
// to a view for a controller
"/help"(controller: "site", view: "help")
}
static mappings = {
"403"(view: "/errors/forbidden")
"404"(view: "/errors/notFound")
"500"(view: "/errors/serverError")
}
grails.org
User Guide
(RTFM: Read The Fantastic Manual)
Stack Overflow
Slack
Github!
Please visit our table for giveaways
and to meet the team!
ociweb.com/grails
info@ociweb.com