Grails' View Layer
Colin Harrington
G3 Summit US 2016
whoami > Colin Harrington
- Grails team at OCI
- @ColinHarrington
- harringtonc@ociweb.com
Title Text
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.
Grails
Spring Boot
spring-web
MVC
MVC
M
(Typically Domain/ GORM)
C
(Controller Layer)
V
(View Layer)
MVC Basics
http://2.bp.blogspot.com/-o7yfxMokYs8/U4Xsxd9dcGI/AAAAAAAAAlQ/ySHAnAiqg0A/s1600/mvc+s.png
Dispatch
- HTTP
- Security / outer filters
- URL Mapping
- Interceptors
- Controller
- Services
- Business logic
-
View Layer
- View Resolution
- View Rendering
Controller Layer
HttpServletResponse
def show() {
response.sendError(418)
}
return a model
def show() {
[book: Book.get(params.id)]
}
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 ])
}
render()
// render the view with the specified model
def theShining = new Book(title: 'The Shining', author: 'Stephen King')
render(view: "viewName", model: [book: theShining])
render text
// 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")
template rendering
// 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)
view rendering
// 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")
Rendering markup
// 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)
}
Converters
// Automatic marshalling of XML and JSON
import grails.converters.*
...
render Book.list(params) as JSON
-----------------------------------------------------------------------
render Book.get(params.id) as XML
Rendering Files
// render a file
render(file: new File(absolutePath), fileName: "book.pdf")
render status code
// render with status code
render(status: 503, text: 'Failed to update book ${b.id}')
Groovy Server Pages
Groovy Servers Pages (or GSP for short) is Grails' view technology. It is designed to be familiar for users of technologies such as ASP and JSP, but to be far more flexible and intuitive.
GSP Basics: scriplets
<html>
<body>
<% out << "Hello GSP!" %>
</body>
</html>
-----------------------------------------------------------------------
<html>
<body>
<%="Hello GSP!" %>
</body>
</html>
-----------------------------------------------------------------------
<html>
<body>
<%-- This is my comment --%>
<%="Hello GSP!" %>
</body>
</html>
GSP Basics: logic
<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 Directives
<%@ page import="java.awt.*" %>
<%@ page contentType="text/markdown" %>
Expressions
<html>
<body>
Hello ${params.name}
</body>
</html>
Taglibs
<g:example />
-----------------------------------------------------------------------
<g:example>
Hello world
</g:example>
-----------------------------------------------------------------------
<g:example attr="${new Date()}">
Hello world
</g:example>
Taglibs
- Built in Taglibs
- Reuse JSP tags (if needed)
- Write your own Taglibs
Built-in Taglibs
- Logic & rendering tags
- Form Handling
- Linking
- Rendering
- Formatting
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
Custom Taglibs
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>
Taglibs: logical
def isAdmin = { attrs, body ->
def user = attrs.user
if (user && checkUserPrivs(user)) {
out << body()
}
}
-----------------------------------------------------------------------
<g:isAdmin user="${myUser}">
// some restricted content
</g:isAdmin>
Taglibs: Iterative
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>
Views
ViewResolver
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;
}
views: location
├── grails-app
│ └── views
│ ├── bar
│ ├── error.gsp
│ ├── index.gsp
│ ├── layouts
│ │ └── main.gsp
│ └── notFound.gsp
./grails-app/views
views: location
./grails-app/views
Found in grails-app/views Filename ending in .gsp
views: convention
class BookController {
def show() {
[book: Book.get(params.id)]
}
}
grails-app/views/book/show.gsp
Resolves to:
views: convention
def show() {
def map = [book: Book.get(params.id)]
render(view: "display", model: map)
}
grails-app/views/book/display.gsp
Resolves to:
views: convention
def show() {
def map = [book: Book.get(params.id)]
render(view: "/shared/display", model: map)
}
grails-app/views/shared/display.gsp
Resolves to:
Templates
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
From a controller
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
Templates- Taglibs
<g:render template="bookTemplate" model="[book:myBook]" />
<tmpl:bookTemplate book="${myBook}" />
Template Namespace
<g:render template="/shared/mySharedTemplate" />
Layouts
Sitemesh
(Used to process Grails' Layouts)
Views --->
Layout --->
Output --->
http://wiki.sitemesh.org/wiki/display/sitemesh3/SiteMesh+3+Overview
Layouts: where?
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>
Layout tags
<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
Triggering Layouts
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>
Merged Output
<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>
Choosing a layout
class BookController {
static layout = 'customer'
def list() { ... }
}
class BookController {
static layout = 'custom/customer'
def list() { ... }
}
Layout by Convention
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
Layout by Convention
- configuration -
grails.sitemesh.default.layout = 'myLayoutName'
Sitemesh Content Blocks
<content tag="navbar">
... draw the navbar here...
</content>
<div id="nav">
<g:applyLayout name="navLayout">
<g:pageProperty name="page.navbar" />
</g:applyLayout>
</div>
GSP Reloading
grails.gsp.enable.reload = true
grails.gsp.view.dir = "/var/www/grails/my-app/"
Content Negotiation
-
Accept header
-
format parameter (or url mapping)
-
/books.json
-
grails.mime.types
-
respond()
package example
class BookController {
def index() {
respond Book.list()
}
}
responseFormats
package example
class BookController {
static responseFormats = ['json', 'html']
def index() {
respond Book.list()
}
}
'format' request param
http://my.domain.org/books?format=xml
"/book/list"(controller:"book", action:"list") {
format = "xml"
}
withFormat()
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 }
}
}
}
via URI Extensions
/book/list.xml
"/$controller/$action?/$id?(.$format)?"{ ... }
matching config via 'grails.mime.types'
Grails Views
- 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 View
json.message {
hello "world"
}
grails-app/views/hello.gson
{"message":{ "hello":"world"}}
JSON View: Models
model {
String message
}
json.message {
hello message
}
@Field String message
json.message {
hello message
}
or using @Field
JSON template
model {
Person person
}
json {
name person.name
age person.age
}
grails-app/views/person/_person.gson
rendering templates
model {
Person person
}
json g.render(template:"person", model:[person:person])
model {
Person person
}
json tmpl.person(person)
Or using the tmpl namespace
rendering Iterables
model {
List<Person> people = []
}
json tmpl.person(people)
[{"name":"Fred",age:10},{"name":"Bob",age:12}]
outputs:
Template Inheritance
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
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.
{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)
HAL Support
model {
Book book
}
json {
hal.links(book)
hal.embedded {
author( book.authors.first() ) { Author author ->
name author.name
}
}
title book.title
}
Markup Views
Markup Views are written in Groovy, end with the file extension gml and reside in the grails-app/views directory.
Markup Views
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>
Groovy Server Pages Beyond HTML
JavaServer Pages (JSP) is a technology that helps software developers create dynamically generated web pages based on HTML, XML, or other document types. Released in 1999 by Sun Microsystems, JSP is similar to PHP and ASP, but it uses the Java programming language.
Other Document Types
- PDF files
- SVG
- Markdown
- images
- video
Groovy Server Pages
HTML is the 99%
What about the other uses?
Rendering SVG:
<%@ 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>
Rendering Markdown
<%@ page contentType="text/markdown" %>
# H1
## H2
### H3
#### H4
##### H5
###### H6
Alternatively, for H1 and H2, an underline-ish style:
Alt-H1
======
Alt-H2
------
UrlMappings.groovy
static mappings = {
"/"(view: "/index") // map the root URL
}
static mappings = {
// to a view for a controller
"/help"(controller: "site", view: "help")
}
UrlMappings.groovy
static mappings = {
"403"(view: "/errors/forbidden")
"404"(view: "/errors/notFound")
"500"(view: "/errors/serverError")
}
Assets
Yea, that's is a whole talk by itself
Resources
-
grails.org
-
User Guide
(RTFM: Read The Fantastic Manual) -
Stack Overflow
-
Slack
-
Github!
-
twitter
Connect with Us
grails.org
groovy-community.grails.org
grailsblog.objectcomputing.com
objectcomputing.com/grails
@grailsframework
@objectcomputing
Thank you for attending!
Please visit our table for giveaways
and to meet the team!
ociweb.com/grails
info@ociweb.com
Grails' View Layer
By Colin Harrington
Grails' View Layer
In years past Grails has taken view technologies forward with GSPs (GroovyServerPages) taglibs and layouts. At its core, Grails3 is a Spring Boot app. We’ll explore what the view layer looks like in modern webapplications delivering HTML, API and other experiences. We’ll look at the grails-views project and cover the options rendering json, markdown and take a look at what it takes to add your own view layer.
- 3,138