Documentos de Académico
Documentos de Profesional
Documentos de Cultura
1/173 - 1%
Introduction
2/173 - 2%
Grails
full stack framework + plugins
Groovy language
Convention over configuration
Object Relational Mapping (GORM) built on top Hibernate
View technology (GSPs)
Controller layer built on Spring MVC
Embedded Tomcat
Dependency Injection with Spring
i18n support
3/173 - 2%
Installation
http://grails.org/Download (grails-1.3.5.zip)
Add GRAILS_HOME environment variable
Add %GRAILS_HOME%\bin to PATH
4/173 - 3%
Test Grails installation
$ grails
Welcome to Grails 1.3.5 - http://grails.org/
Licensed under Apache Standard License 2.0
Grails home is set to: /home/miguel/.grails
5/173 - 3%
First Grails application
6/173 - 4%
Create application
$ grails create-app tutorial
$ cd tutorial
7/173 - 5%
Create controller
$ grails create-controller hello
8/173 - 5%
grails-app/controllers/tutorial
/HelloController.groovy
package tutorial
class HelloController {
def world = {
render "Hello World!"
}
}
9/173 - 6%
Run application
$ grails run-app
10/173 - 6%
browse http://localhost:8080/tutorial
11/173 - 7%
IDE
- IntelliJ IDEA
grails integrate-with --intellij
- NetBeans (grails supported)
- Eclipse (SpringSource Tool Suite, STS)
- TextMate
grails integrate-with --textmate
12/173 - 7%
Grails directory structure
grails-app - top level app dir
conf - Configuration files
controllers - web controllers
domain - application domain models
i18n - i18n resource files
services - services layer
taglib - custom tag libraries
views - Groovy Server Pages
scripts - scripts for grails
src - Supporting sources
test - unit, integration and functional tests
13/173 - 8%
Grails commands
14/173 - 9%
Running a Grails application
$ grails run-app
15/173 - 9%
Testing a Grails application
$ grails test-app
16/173 - 10%
Deploying a Grails application
$ grails war
17/173 - 10%
Scaffolding
$ grails generate-all tutorial.Hello
18/173 - 11%
Creating artifacts
$ grails create-controller
$ grails create-domain-class
$ grails create-unit-test
$ grails create-tag-lib
19/173 - 11%
Configuration
20/173 - 12%
Basic configuration
grails/conf/Config.groovy
21/173 - 13%
custom configuration
set:
my.app.value = "some value"
read (controller/taglibs):
grailsApplication.config.my.app.value
import org.codehaus.groovy.grails.commons.*
CodeHolder.config.my.app.hello
22/173 - 13%
logging
log4j = {
error 'package1', 'package2'
warn 'package3'
}
23/173 - 14%
logging packages
org.codejaus.groovy.grails.commons - class loading
org.codejaus.groovy.grails.web - web request processing
org.codejaus.groovy.grails.web.mapping - url mapping
org.codejaus.groovy.grails.plugins - plugin activity
org.springframework - spring activity
org.hibernate - hibernate activity
24/173 - 14%
GORM
grails.gorm.failOnError
25/173 - 15%
Environments
per environment configuration
- Config.groovy
- DataSource.groovy
preset
- dev
- test
- prod
26/173 - 16%
Environments in command line
grails [environment] [command name]
grails test war // creates war for the test
//environment
27/173 - 16%
Programmatic environment
detection
import grails.util.Environment
switch(Environment.current) {
case Environment.DEVELOPMENT:
someConfigForDev()
break
case Environment.PRODUCTION:
someConfigForProd()
break
}
28/173 - 17%
Per environment bootstrap
def init = { ServletContext ctx ->
environments {
production {
ctx.setAttribute("env", "prod")
}
development {
someConfigForDev()
}
}
someConfigForAllEnvironments()
}
29/173 - 17%
Environments in application code
import grails.util.Environment
Environment.executeForCurrentEnvironment {
production {
someConfig()
}
development {
someOtherConfig()
}
}
30/173 - 18%
DataSource
JDBC
- put jar in grails project lib/ directory
- environment aware
- use a runtime dependency
dependencies {
// mysql
runtime 'mysql:mysql-connector-java:5.1.5'
// sqlserver
runtime 'net.sourceforge.jtds:jtds:1.2.4'
}
31/173 - 18%
JDBC configuration
- driverClassName
- username
- password
- url
- dbCreate
- pooled
- logSql
- dialect
- properties
- jndiName
32/173 - 19%
JDBC configuration example
dataSource {
pooled = true
dbCreate = "update"
url = "jdbc:mysql://localhost/yourDB"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "yourUser"
password = "yourPassword"
properties {
maxActive = 50
maxIdle = 25
minIdle = 5
initialSize = 5
minEvictableIdleTimeMillis = 60000
timeBetweenEvictionRunsMillis = 60000
maxWait = 10000
validationQuery = "/* ping */"
}
}
33/173 - 20%
Externalized configuration
configuration
grails.config.locations = [
"classpath:${appName}-config.properties",
"classpath:${appName}-config.groovy",
"file:${userHome}/.grails/${appName}-config.properties",
"file:${userHome}/.grails/${appName}-config.groovy" ]
read:
grailsApplication
34/173 - 20%
Versioning
//set
$ grails set-version 0.99
application.properties
//read in controllers
def version = grailsApplication.metadata['app.version']
def grailsVer = grailsApplication.metadata['app.grails.version']
35/173 - 21%
Documentation
- Textile variation
- src/doc/guide
1. first chapter.gdoc
2. this will be the second chapter.gdoc
36/173 - 21%
Dependency resolution
- Repositories
- maven
- directory
- Scope
- build
- compile
- runtime
- test
- provided
37/173 - 22%
Configuration
// group:name:version
runtime "com.mysql:mysql-connector-java:5.1.5"
runtime(group: 'com.mysql',
name: 'mysql-connector-java',
version: '5.1.5')
// plugin dependencies
plugins {
test ':spock:0.5-groovy'
runtime ':jquery:1.4.2.7'
}
38/173 - 22%
Command line
39/173 - 23%
Gant
- Groovy wrapper around Apache Ant
- Search locations:
- USER_HOME/.grails/scripts
- PROJECT_HOME/scripts
- PROJECT_HOME/plugins/*/scripts
- GRAILS_HOME/scripts
- example
$ grails run-app
- searches:
- USER_HOME/.grails/scripts/RunApp.groovy
- PROJECT_HOME/scripts/RunApp.groovy
- PLUGINS_HOME/*/scripts/RunApp.groovy
- GLOBAL_PLUGINS_HOME/*/scripts/RunApp.groovy
- GRAILS_HOME/scripts/RunApp.groovy
40/173 - 24%
Ant
- $ grails integrate-with --ant
- build.xml
- ivy.xml
- ivisettings.xml
- CuiseControl/Hudson
41/173 - 24%
GORM
42/173 - 25%
Domain classes
- hold state about business processes
- implement behavior
- relationships between domain classes
- one-to-one
- one-to-many
43/173 - 25%
GORM
- Grails' Object Relational Mapping (ORM)
- Hibernate 3
44/173 - 26%
Create DB MySQL
mysql -uroot -p
create database tutorial;
create user tutorial@localhost identified by 'tutorial';
grant all on tutorial.* to tutorial@localhost;
45/173 - 27%
Config database connection
grails-app/conf/DataSource.groovy:
environments {
development {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
//loggingSql = true
url = "jdbc:mysql://localhost/tutorial"
driverClassName = "com.mysql.jdbc.Driver"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
username = "tutorial"
password = "tutorial"
}
}
}
46/173 - 27%
Enable Maven remote
grails-app/conf/BuildConfig.groovy:
mavenCentral()
47/173 - 28%
Add dependency
grails-app/conf/BuildConfig.groovy:
dependencies {
runtime 'mysql:mysql-connector-java:5.1.5'
}
48/173 - 28%
Demo
$ grails create-app tutorial // default package: tutorial
49/173 - 29%
grails-app/domain/tutorial
/Person.groovy
package tutorial
class Person {
static constraints = {
}
}
50/173 - 29%
grails-app/domain/tutorial
/Person.groovy
package tutorial
class Person {
String name
Integer age
Date lastVisit
static constraints = {
}
}
51/173 - 30%
Example
//grails console
import tutorial.*
//save
def p = new Person(name: 'Miguel', age: 31, lastVisit: new Date())
p.save()
// read
p = Person.get(1)
println p.name
//update
p = Person.get(1)
p.name = "Bob"
p.save()
//delete
p = Person.get(1)
p.delete()
//list
def l = Person.list()
l.each {
println 52/173 - 31%
Relationships
- relationship
- define how domain classes interact
with each other
- unless specified in both ends, exists only
in the direction it is defined
- cardinality
one-to-one
one-to-many
many-to-many
- direction
- unidirectional
- bidirectional
53/173 - 31%
Example domain classes
$ grails create-domain-class Face
$ grails create-domain-class Nose
$ grails create-domain-class Book
$ grails create-domain-class Author
54/173 - 32%
one-to-one 1
class Face {
Nose nose // property
}
class Nose {
}
55/173 - 32%
mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| nose_id | bigint(20) | NO | MUL | NULL | |
+---------+------------+------+-----+---------+----------------+
56/173 - 33%
one-to-one 2
class Face {
Nose nose
static constraints = {
nose unique: true
}
}
class Nose {
}
58/173 - 34%
one-to-one 3
class Face {
Nose nose
}
class Nose {
static belongsTo = [ face:Face ]
}
// bidirectional (Face <-> Nose)
// many-to-one (Many faces have can have a given nose)
59/173 - 35%
mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| nose_id | bigint(20) | NO | MUL | NULL | |
+---------+------------+------+-----+---------+----------------+
60/173 - 35%
behavior
- insert/updates cascade from Face to Nose
// Nose is saved automatically
new Face(nose: new Nose()).save()
61/173 - 36%
one-to-one 3
class Face {
static hasOne = [ nose:Nose ]
}
class Nose {
Face face
}
62/173 - 36%
mysql> describe face;
+---------+------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
+---------+------------+------+-----+---------+----------------+
63/173 - 37%
one-to-many
class Author {
static hasMany = [ books:Book ]
String name
}
class Book {
String title
}
65/173 - 38%
Example
import tutorial.*
a.books.each {
println it.title
}
66/173 - 39%
mysql> select * from author;
+----+---------+---------+
| id | version | name |
+----+---------+---------+
| 1 | 0 | Tolkien |
+----+---------+---------+
67/173 - 39%
behavior
// save/update are cascaded
// deletes are not cascaded
import tutorial.*
def a = Author.get(1)
a.delete()
Author.list().size() // 0
Book.list().size() // 2
68/173 - 40%
mysql> select * from author;
Empty set (0.00 sec)
69/173 - 40%
one-to-many 2
class Author {
static hasMany = [ books:Book ]
String name
}
class Book {
static belongsTo = [ author:Author ]
String title
}
70/173 - 41%
mysql> describe author;
+---------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+---------+--------------+------+-----+---------+----------------+
| id | bigint(20) | NO | PRI | NULL | auto_increment |
| version | bigint(20) | NO | | NULL | |
| name | varchar(255) | NO | | NULL | |
+---------+--------------+------+-----+---------+----------------+
71/173 - 42%
Example
import tutorial.*
a.addToBooks(b)
a.addToBooks(b2)
a.save()
println(a.books.size()) // 2
a.books.each {
println it.title
}
// The Hobbit
// The Lord of the Rings
println b.author.name // Tolkien
println b2.author.name // Tolkien
72/173 - 42%
mysql> select * from author;
+----+---------+---------+
| id | version | name |
+----+---------+---------+
| 1 | 0 | Tolkien |
+----+---------+---------+
73/173 - 43%
many-to-many
// hasMany on both sides
// belongsTo on the owned (subordinated) side of the relationship
// owning side takes responsibility for persisting relationship
// owning side cascade saves
// use a join table
class Book {
static belongsTo = Author
static hasMany = [ authors:Author ]
String title
}
class Author {
static hasMany = [ books:Book ]
String name
}
74/173 - 43%
Example
import tutorial.*
println Author.list().size() // 1
println Book.list().size() // 2
75/173 - 44%
mysql> select * from author;
+----+---------+---------+
| id | version | name |
+----+---------+---------+
| 1 | 0 | Tolkien |
+----+---------+---------+
76/173 - 44%
Example
import tutorial.*
println Author.list().size() // 1
println Book.list().size() // 3
77/173 - 45%
mysql> select * from author;
+----+---------+---------+
| id | version | name |
+----+---------+---------+
| 1 | 0 | Tolkien |
+----+---------+---------+
78/173 - 46%
Save/update
def p = Person.get(1)
p.name = "Bob"
p.save()
def p = Person.get(1)
p.name = "Bob"
p.save(flush: true) // Forces a synchronization with DB
// same as save/update
def p = Person.get(1)
p.delete(flush:true) // Forces synchronization with DB
// Handling exceptions
def p = Person.get(1)
try {
p.delete(flush:true)
} catch(org.springframework.dao.DataIntegrityViolationException e) {
// deal with exception
}
80/173 - 47%
Tips
http://blog.springsource.com/2010/06/23/gorm-gotchas-part-1/
81/173 - 47%
Eager and lazy fetching
// by default lazy
class Location {
String city
}
class Author {
String name
Location location
}
Author.list().each { author ->
println author.location.city
}
82/173 - 48%
Eager loading
class Author {
String name
Location location
static mapping = {
location fetch: 'join'
}
}
83/173 - 48%
Eager loading
// Dynamic finders #
Author.findAllByNameLike("John%",
[ sort: 'name',
order: 'asc',
fetch: [location: 'join'] ]).each { a ->
// ...
}
//Criteria queries
def authors = Author.withCriteria {
like("name", "John%")
join "location"
}
84/173 - 49%
Querying
//list
def books = Book.list()
def books = Book.list(offset:10, max:20)
def books = Book.list(sort:"title", order: "desc")
//retrieval
def b = Book.get(23)
def list = Book.getAll(1,3,24)
85/173 - 50%
Dynamic finders
class Book {
String title
Date releaseDate
Author author
}
class Author {
String name
}
def b
b = Book.findByTitle("The Hobbit")
b = Book.findByTitleLike("%Hobb%")
b = Book.findByReleaseDateBetween(firstDate, secondDate)
b = Book.findByReleaseDateGreaterThan(someDate)
b = Book.findByTitleLikeOrReleaseDateLessThan("%obbi%", someDate)
b = Book.findByReleaseDateIsNull()
b = Book.findByReleaseDateIsNotNull()
b = Book.findAllByTitleLike("The %",
[max:3, offset:10, sort: "title", order: "desc"])
86/173 - 50%
Criteria
def c = Book.createCriteria()
def results = c {
eq("releaseDate", someDate)
or {
like("title", "%programming%")
like("title", "%Ring%")
}
maxResults(100)
order("title", "desc")
}
87/173 - 51%
HQL
def list = Book.findAll(
"from Book as b where b.title like 'Lord of the%'")
def list = Book.findAll(
"from Book as b where b.author = ?",
[author])
def list = Book.findAll(
"from Book as b where b.author = :author",
[author:someAuthor])
88/173 - 51%
Controllers
89/173 - 52%
Controllers
- handle request
- create response
- can generate the response
- delegate to a view
- scope: request (a _new_ instance is created for
each client request)
- Class with Controller suffix
- grails-app/controllers
90/173 - 53%
Create controller
$ grails create-controller book // default package: tutorial
91/173 - 53%
grails-app/controllers/tutorial
/BookController.groovy
package tutorial
class BookController {
def index = {}
}
92/173 - 54%
Controller actions
// properties that are assigned a block of code
// each property maps to an URI
// public by default
class BookController {
def list = {
// some statements
}
}
// maps to /book/list
93/173 - 54%
Default action
// 1. if only one action exists, the default URI
// maps to it
// 2. if an index action exists, it handle request
// when no action specified
// 3. explicit declaration
static defaultAction = "list"
94/173 - 55%
Scopes
servletContext - application wide
session - session of a user
request - current request
params - _mutable_ map of incoming request params
flash - only for this request and the subsequent
(e.g. set a message before redirect)
95/173 - 55%
Accessing scopes
class BookController {
def find = {
def findBy = params["findBy"]
def userAgent = request.getHeader("User-Agent")
def loggedUser = session["logged_user"]
// session.logged_user
}
def delete = {
def b = Book.get( params.id )
if(!b) {
flash.message = "User not found for id ${params.id}"
redirect(action:list)
}
}
}
96/173 - 56%
Models and views
Model
- Map of objects that the view uses to render the
response
- Keys of map translate to variables in the view
97/173 - 57%
Explicit return of model
def show = {
[ book: Book.get(params.id) ]
}
98/173 - 57%
Implicit return of mode
class BookController {
List books
List authors
def list = {
books = Book.list()
authors = Author.list()
}
}
99/173 - 58%
Implicit view
class BookController {
def show = {
[ book:Book.get(params.id) ]
}
}
// grails look for view at
// grails-app/views/book/show.gsp (show.jsp first)
100/173 - 58%
Explicit view
def show = {
def map = [ book: Book.get(1) ]
render(view: "display", model: map)
}
def show = {
def map = [ book: Book.get(1) ]
render(view: "/shared/display", model: map)
}
101/173 - 59%
Direct rendering of the response
class BookController {
def greet = {
render "hello!"
}
}
102/173 - 59%
Redirect
class BookController {
def greet = {
render "hello!"
}
def redirect = {
redirect(action: greet)
}
}
103/173 - 60%
Redirect expects
- other closure on the same class
redirect(action:list)
- controller and action
redirect(controller: 'author', action: 'list')
- URI
redirect(uri: "/help.html")
- URL
redirect(url: 'http://yahoo.com')
104/173 - 61%
Data binding
//implicit constructor
def save = {
def b = new Book(params)
b.save()
}
//explicit binding
def save = {
def b = Book.get(params.id)
b.properties = params // sets every parameter
// as a property in the object
b.someParam = params.foo // only some parameters are set
b.otherParam = params.bar
b.save()
}
105/173 - 61%
JSON and XML responses
106/173 - 62%
XML
def listXML = {
def results = Book.list()
render(contentType: 'text/xml') {
books {
for(b in results) {
book(title: b.title)
}
}
}
}
107/173 - 62%
<books>
<book title="title one"/>
<book title="title two"/>
</books>
108/173 - 63%
JSON
def listJSON = {
def results = Book.list()
render(contentType: 'text/json') {
books = array {
for(b in results) {
book(title: b.title)
}
}
}
}
109/173 - 64%
"books":[
{"title": "title one"},
{"title": "title two"}
]
110/173 - 64%
Automatic XML and JSON
marshaling
111/173 - 65%
XML
import grails.converters.*
def list = {
render Book.list() as XML
}
def list2 = {
render Book.list().encodeAsXML() // using codecs
}
112/173 - 65%
JSON
render Book.list() as JSON
render Book.list().encodeAsJSON()
113/173 - 66%
Type converters
def total = params.int('total')
def checked = params.boolean('checked')
// null safe
// safe from parsing errors
114/173 - 66%
Groovy Server Pages
115/173 - 67%
GSP
similar to ASP, JSP
more flexible
live in grails-app/views
rendered automatically (by convention) or with the render method
Mark-up (HTML) and GSP tags
embedded logic possible but discouraged
uses the model passed by the controller action
116/173 - 68%
Controller
// returns a model with key called book.
def show = {
[ book: Book.get(1) ]
}
117/173 - 68%
View
<%-- the key named book from the model is referenced by
name in the gsp --%>
<%= book.title %>
118/173 - 69%
GSP Basics
<% %> blocks to embed groovy code (discouraged)
<html>
<body>
<% out << "Hello world!" %>
<%= "this is equivalent" %>
</body>
</html>
119/173 - 69%
Variables in GSPs
<% now = new Date() %>
...
<p>Time: <%= now %></p>
<!--
predefined:
- application
- applicationContext
- flash
- grailsApplication
- out
- params
- request
- response
- session
- webRequest
-->
120/173 - 70%
GSP Expressions
${expression}
<html>
<body>
Hello ${params.name}
Time is: ${new Date()}
2 + 2 is: ${ 2 + 2 }
but also: ${ /* any valid groovy statement */ }
</body>
</html>
121/173 - 70%
GSP built-in tags
- start with g: prefix
- no need to import tag libraries
<g:example param="a string param"
otherParam="${new Date()}"
aMap="[aString:'a string', aDate: new Date()]">
Hello world
</g:example>
122/173 - 71%
<g:set/>
<g:set var="myVar" value="${new Date()}"/>
<g:set var="myText">
some text with expressions ${myVar}
</g:set>
<p>${myText}</p>
123/173 - 72%
Logic/Iteration
<g:if test="${1 > 3}">
some html
</g:if>
<g:else>
something else
</g:else>
<g:each in="${tutorial.Book.list()}" var="b">
title: ${b.title}
</g:each>
125/173 - 73%
Forms and fields
<g:form name="myForm"
url="[controller:'book', action:'submit']">
Text: <g:textField name="text"/>
<g:submitButton name="button" value="Submit form"/>
</g:form>
fields
- textField
- checkbox
- radio
- hiddenField
- select
- submitButton
126/173 - 73%
Tags as method calls
In views
Static Resource: ${createLinkTo(dir:"images", file:"logo.jpg")}
127/173 - 74%
Templates
- maintainable and reusable chunks of views
- name starts with a underscore: _myTemplate.gsp
grails-app/views/book/_info.gsp
<div class="book" id="${book.id}">
Title: ${book.title}
</div>
from other view
<g:render template="info"
var="book"
collection="${tutorial.Book.list()}"/>
from controller
def useTemplate = {
def b = Book.get(1)
render(template: "info", model: [ book:b ])
}
128/173 - 74%
GSP debugging
GSPs
- Add "showSource=true" to the url. Only in development mode
Templates
Add "debugTemplates" to the url. Only in development mode
129/173 - 75%
Tag libraries
groovy class that ends with TagLib
live in grails-app/taglib
130/173 - 76%
Taglibs 1
Taglib
class UtilsTagLib {
def copyright = { attrs, body ->
out << "© Copyright 2010"
}
}
view
<div><g:copyright/></div>
131/173 - 76%
Example 2
Taglib
import java.text.*
def dateFormat = { attrs, body ->
def fmt = new SimpleDateFormat(attrs.format)
out << fmt.format(attrs.date)
}
view
<g:dateFormat format="dd-MM-yyyy" date="${new Date()}"/>
132/173 - 77%
Example 3
Taglib
def formatBook = { attrs, body ->
out << render(template: "info", model: [ book:attrs.book ])
}
view
<div><g:formatBook book="${tutorial.Book.get(1)}"/></div>
133/173 - 77%
AJAX
134/173 - 78%
Javascript
<head>
<g:javascript library="prototype"/>
<g:javascript library="scriptaculous"/>
</head>
135/173 - 79%
Remote link
view
<g:remoteLink action="delete" id="1">
Delete Book
</g:remoteLink>
def delete = {
def b = Book.get(params.id)
b.delete()
render "Book ${b.title} was deleted"
}
}
136/173 - 79%
Independent updates for failure and success
View
Success: <div id="success"></div>
Failure: <div id="error"></div>
<g:remoteLink action="success"
update="[success:'success', failure:'error']">
Success ajax request
</g:remoteLink><br/>
<g:remoteLink action="failure"
update="[success:'success', failure:'error']">
Failure ajax request
</g:remoteLink>
137/173 - 80%
Independent updates for failure and success
Controller
def success = {
render status:200, text:"request OK"
}
def failure = {
render status:503, text:"request failed"
}
138/173 - 80%
Ajax form submission
view
<g:formRemote url="[controller:'ajax', action:'ajaxAdd']"
name="ajaxForm"
update="[success:'addSuccess', failure:'addError']">
Book title: <input type="text" name="title"/>
<input type="submit" value="Add Book!" />
</g:formRemote >
<div id="addSuccess"></div>
<div id="addError"></div>
controller
def ajaxAdd = {
def b = new Book(params)
b.save()
render "Book '${b.title}' created"
}
139/173 - 81%
Ajax returning content
view
<g:remoteLink action="ajaxContent"
update="book">
Update Content
</g:remoteLink>
<div id="book"><!--existing book mark-up --></div>
controller
def ajaxContent = {
def b = Book.get(2)
render "Book: <strong>${b.title}</strong> found at ${new Date()}!"
}
140/173 - 81%
Ajax returning JSON
view
<g:javascript>
function updateBook(e) {
// evaluate the JSON
var book = eval("("+e.responseText+")")
$("book_title").innerHTML = book.title
}
</g:javascript>
<g:remoteLink action="ajaxData"
update="foo"
onSuccess="updateBook(e)">
Update Book with JSON
</g:remoteLink>
<div id="book">
<div id="book_title">The Hobbit</div>
</div>
controller
import grails.converters.*
def ajaxData = {
def b = new Book(title: 'new book title').save()
render b as JSON
141/173 - 82%
Version Control
142/173 - 83%
Subversion
Create repository
$ svnadmin create /home/miguel/repo
$ svn list file:///home/miguel/repo
Checkout
$ cd ..
$ svn co file:///home/miguel/repo/GrailsProject/trunk OtherUserGrailsProject
$ grails upgrade
143/173 - 83%
Git
Create repository
$ grails create-app GrailsProject
$ cd GrailsProject
$ git init
.gitignore:
web-app/WEB-INF/
target/
$ git add .
$ git commit -m "First commit on GrailsProject"
Clone
$ cd ..
$ git clone GrailsProject OtherUserGrailsProject
$ grails upgrade
144/173 - 84%
Thanks
Miguel Cobá
miguel@trantaria.com
Groovy Intro by Miguel Cobá is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
145/173 - 84%
Grails Advanced
146/173 - 85%
Advanced
147/173 - 85%
Excel
148/173 - 86%
Options
Apache POI
149/173 - 87%
Excel with Apache POI
Source
http://www.michaelvanvliet.nl/node/4
Get POI
- http://www.apache.org/dyn/closer.cgi/poi/release/
- unzip package
- put *.jar in tutorial/lib
- grails create-controller excelPOI
150/173 - 87%
package tutorial
import org.apache.poi.hssf.model.Workbook;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.hslf.model.Sheet
import org.apache.poi.hssf.record.formula.functions.Row
class ExcelPOIController {
def index = {
def nameOfWorkbook = 'workbook.xls'
def locationToStoreWorkbook = '/tmp/' // Or C:\temp
def wb = new HSSFWorkbook()
def sheet = wb.createSheet("Demo")
def row = sheet.createRow((short)0)
row.createCell(1).setCellValue('Column 1')
row.createCell(2).setCellValue('Column 2')
row = sheet.createRow((short)1);
row.createCell(0).setCellValue('Row 1')
row.createCell(1).setCellValue('Hello')
row.createCell(2).setCellValue('World')
def fileWorkbook = new FileOutputStream(locationToStoreWorkbook +
nameOfWorkbook)
wb.write(fileWorkbook)
fileWorkbook.close()
render('Workbook was saved to: ' + locationToStoreWorkbook +
nameOfWorkbook)
}
}
151/173 - 88%
Web Services
152/173 - 88%
Options
REST: Grails only
153/173 - 89%
SOAP with XFire plugin
Source
http://groovy.codehaus.org/Using+the+Grails+XFire+plugin+and+GroovyWS
Install XFire plugin
$ grails install-plugin xfire
$ grails create-service test
154/173 - 90%
Test Service
package tutorial
class TestService {
static expose=['xfire']
static conversions = [
'AUD': [ 'USD': 100.00D, 'GBP': 44.44D ],
'USD': [ 'AUD': 1.00D, 'GBP': 88.88D ],
'GBP': [ 'AUD': 22.22D, 'USD': 33.33D ]
]
155/173 - 90%
WSDL
http://localhost:8080/tutorial/services/test?wsdl
156/173 - 91%
Web Service consumer
client.groovy
import groovyx.net.ws.WSClient
157/173 - 91%
Consuming the service
$ groovy client.groovy
10 AUD are worth 1000.0 USD
158/173 - 92%
Jasper
159/173 - 92%
Options
Grails jasper plugin
160/173 - 93%
Reports with dynamic-jasper plugin
Source
http://www.grails.org/plugin/dynamic-jasper
Install dynamic-jasper plugin
$ grails install-plugin dynamic-jasper
$ grails create-domain-class User
$ grails create-controller user
161/173 - 94%
User controller class
package tutorial
class UserController {
162/173 - 94%
User domain class
package tutorial
class User {
String username
String name
String lastName
Boolean active
static reportable = [:]
static constraints = {
}
}
163/173 - 95%
View a report
http://localhost:8080/tutorial/djReport/index?entity=user
164/173 - 95%
jQuery
165/173 - 96%
Options
Thousands of jQuery libraries
jQuery UI
jQuery Tools
166/173 - 96%
jQuery Tools
Source
http://flowplayer.org/tools/download/index.html
Install jQuery Tools
<script src="http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js">
</script>
167/173 - 97%
grails-app/view/book/jquery.gsp
<html>
<head>
<script src="http://cdn.jquerytools.org/1.2.5/full/jquery.tools.min.js">
</script>
</head>
<body>
</body>
</html>
168/173 - 98%
Book Controller
class BookController {
def jquery = {}
}
169/173 - 98%
Date Input
<!-- dateinput styling -->
<link rel="stylesheet"
type="text/css"
href="http://static.flowplayer.org/tools/
demos/dateinput/css/skin1.css"/>
<!-- HTML5 date input -->
<input type="date" />
170/173 - 99%
jQuery Tools
http://jqueryui.com/
http://jqueryui.com/demo
- Accordion
- Autocomplete
- Button
- Dialog
- Datepicker
- Progressbar
- Tabs
- Slider
171/173 - 99%
Security
Spring Security Core Plugin
http://www.grails.org/plugin/spring-security-core
http://burtbeckwith.github.com/grails-spring-security-core/
- Spring Security
- DB
- LDAP
- OpenID
- CAS
172/173 - 100%
Thanks
Miguel Cobá
miguel@trantaria.com
Groovy Intro by Miguel Cobá is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License.
173/173 - 100%