Grails' View Layer

 

 

Colin Harrington

G3 Summit US 2016

whoami > Colin Harrington

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,165