You are on page 1of 62

Griffon in Front

Grails in Back
Leveraging Grails with Griffon
Griffon in Front
Grails in Back
Leveraging Grails with Griffon
Abstract
Groovy and Grails have given us the ability to leverage the
strength of the Java Platform (and Eco System) and the
productivity of Convention over Configuration to construct
websites. But What If the User Interface requirements of the
new application is best solved with the type of interaction a
desktop application provides?

Griffon bring the same productivity gains to the desktop


application that Grails brings to web applications. This session
will use Griffon and popular open source libraries to build a
desktop applicaiton to interact with a Grails backend.
Introduction
My name is Jim Shingler
Chief Technical Architect
President of Genuine Solutions
Beginning Groovy and Grails
Co-Author
FallME (Inversion of Control for JavaME)
Co-Founder
Griffon Founders
Griffon Founders

Danno Ferrin
http://shemnon.com/speling

Andres Almiray
http://jroller.com/aalmiray

James Williams
http://jameswilliams.be/blog
Common Complaints
about Swing
Its hard
Have to code too much
Code is complex
Not many Advanced GUI Components (Myth)
Start Small
Swing and SwingX Builder
GUI Components
About Box
Define and Process Actions
Builders
The Builder Pattern is a software design pattern. The
intention is to separate the construction of a
complex object from its representation so that the
same construction process can create different
representations.

Often, the Build Patter is used to build Products in


accordance to the Composite pattern.
Source: Wikipedia Swing is a complex hierarchy, . . .
Sounds like an opportunity
GUI Builders
SwingBuilder
Applies the Builder Pattern to the construction of Swing
Makes Swing Groovy

SwingXBuilder
Extends SwingBuilder adding the SwingLabs Components

JideBuilder
Extends SwingBuilder adding JIDE Components

SWTBuilder
Applies the Builder Pattern to the construction of SWT
Makes SWT Groovy
Plugins
A plugin is a Griffon extension point. Conceptually, it
is similar to the plugins found in modern IDEs.
A plugin is a technique to encapsulate functionality
that can be reused across multiple applications.
Griffons plugin community has just begun but it is
growing fast.
See: >griffon list-plugins

http://grails.org/Plugins
http://www.grails.org/The+Plug-in+Developers+Guide
DEMO
Create Count App
Add Button
Build and Initialize Click Action
Process the Click Action
Install and Enable SwingXBuilder
Build and Initialize Menus
Build and Initialize Menu Actions
Process the Menu Actions
DEMO
Create Count App
Add Button
Build and Initialize Click Action
Process the Click Action
Install and Enable SwingXBuilder
Build and Initialize Menus
Build and Initialize Menu Actions
Process the Menu Actions NOTE:
The Code included on the
next several pages has
been enhanced based
upon questions asked in
the session.
Controller
import javax.swing.JOptionPane

class Sample2Controller {
// these will be injected by Griffon
def model
def view

void mvcGroupInit(Map args) {


// this method is called after model and view are injected
}

def click = { evt = null ->


model.count++
}

def exit = { evt = null ->


System.exit(0)
}

def showAbout = { evt = null ->


JOptionPane.showMessageDialog(null,
'''This is the SimpleUI Application''')
}
}
Model
import groovy.beans.Bindable

@Bindable
class Sample2Model {
def count = 0
}
View
application(title:'sample2', /*size:[320,480], */location:[200,200],
pack:true, locationByPlatform:false) {
// add content here
build(Actions)
build(MenuBar)
button(id:'clickButton', text:bind{ model.count }, action: clickAction)
}

MenuBar
jxmenuBar {
menu(text: 'File', mnemonic: 'F') {
menuItem(exitAction)
}

glue()
menu(text: 'Help', mnemonic: 'H') {
menuItem(aboutAction)
}
}
Actions
// create the actions
action(id: 'clickAction',
name: 'Click Me',
closure: controller.&click,
shortDescription: 'Increment the Click Count'
)

action(id: 'exitAction',
name: 'Exit', closure: controller.exit,
mnemonic: 'x', accelerator: 'F4',
shortDescription: 'Exit SimpleUI'
)

action(id: 'aboutAction',
name: 'About', closure: controller.showAbout,
mnemonic: 'A', accelerator: 'F1',
shortDescription: 'Find out about SimpleUI'
)
Goal
Goal
Menu Bar
Tool Bar

Content Area

Status Bar
Goal
Menu Bar
Tool Bar
Login Dialog

Content Area

Tips Dialog

Status Bar
Planning
Menu Bar

Tool Bar

Status Bar

About Box

Tips

Login Dialog

Master / Detail Content Area


Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box

Tips

Login Dialog

Master / Detail Content Area


Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box

Tips

Login Dialog

Master / Detail Content Area


Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area


Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area


Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area Glazed Lists

Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area Glazed Lists

Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area Glazed Lists

Table and Fields

Wire it together with Actions

Make calls to Web Services


Planning
Menu Bar
Tool Bar

Status Bar

About Box
Tips

Login Dialog

Master / Detail Content Area Glazed Lists

Table and Fields

Wire it together with Actions

Make calls to Web Services


Organization
MVC Triad
Controller View

Model
Organization
MVC Triad
Controller View

Model

Griffon Framework
Organization
Controller View

Model

Griffon Framework
Organization
Menu Bar
Controller View

Model

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Model

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model
Tool Bar

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model
Tool Bar

Status Bar

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model
Tool Bar
Services
Status Bar

Griffon Framework
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model
Tool Bar
Services
Status Bar

Http Utils
Get
Put
Post Griffon Framework
Delete
Organization
Menu Bar
Controller View
About Dialog

Content Pane
Model
Tool Bar
Services
Status Bar

Http Utils
Get
Put
Post Griffon Framework
Delete

Rest Controller
Interaction with
RESTful WebServices
SQL HTTP Grails
Action
Method Method Convention
Create INSERT PUT create
Read SELECT GET show
Update UPDATE POST update
Delete DELETE DELETE delete
Collect SELECT list
Loading Data
class GCollabTodoController {
. . .

void loadData() {
setStatus("Loading Data")
busy

model.todos.clear()
model.todos.addAll (TodoService.list(appContext))

norm
setStatus("Finished Loading Data")
}
class TodoService {
static String APP_URL = 'http://localhost:8080/collab-todo/json/todo'

static List list(appContext) {


def userContext = appContext.userContext

def get = new Get(url: APP_URL,


userName: userContext.userName,
password: userContext.password)

def todoJson = get.text


def str = JsonUtil.makeJSONStrict(todoJson)
def jsonarray = JSONSerializer.toJSON(str)

def todo
def outputList = []
jsonarray.each {
todo = JsonUtil.jsonToObject(it.toString(), Todo.class)
outputList.add todo
}
return outputList
}
Lets Look at the Code
class Get{
String url
QueryString queryString = new QueryString()
String text

Get (HttpUtils)
def userName
def password

String getText()
try {
def response
def conn = new URL(toString()).openConnection()
conn.requestMethod = "GET"
conn.doOutput = true

if (userName && password) {


conn.setRequestProperty("Authorization", "Basic ${userName}:${password}")
}

def content
if (conn.responseCode == conn.HTTP_OK) {
response = conn.content.text
} else {
response = "URL: " + this.toString() + "\n" +
"RESPONSE CODE: " + conn.responseCode
throw new ResourceException(response)
}
conn.disconnect()
return response
} catch (Exception e) {
println "Caught Exception ${e}"
throw new ResourceException(e)
}
}

String toString(){
return url + "?" + queryString.toString()
}
void saveTodo(event) {
fillSelectedTodoFromView()

def todo = selectedTodo()


Save Todo
if(shouldBeSaved(todo)) {
execWithExceptionHandling {
TodoService.save(appContext, todo)
loadData()
}
} else {
JOptionPane.showMessageDialog(frame,
"If this had been a completed application the Todo would have been updated:")
}
}

private boolean shouldBeSaved(todo) {


if (todo.id == "" || !todo.id ) {
return true
}
return false
}

private void fillSelectedTodoFromView() {


selectedTodo().with {
name = view.nameTextField?.text
priority = view.priorityComboBox?.selectedItem
selectedTodo().status = view.statusComboBox?.selectedItem
completedDate = view.completedDateField?.date
createdDate = view.createDateField?.date
dueDate = view.dueDateField?.date
note = view.noteTextField?.text
}
}
Service Save
static void save(appContext, todo) {
def userContext = appContext.userContext

def put = new Put(url: APP_URL,


userName: userContext.userName,
password: userContext.password)

put.queryString.add("name", todo.name)
put.queryString.add("priority", todo.priority)
put.queryString.add("status", todo.status)
put.queryString.add("note", todo.note)
put.queryString.add("owner.id", userContext.id)
put.queryString.addDate("createdDate", todo.createdDate)

def putResponse = put.text


}
package http.utils
class Put{
String url
QueryString queryString = new QueryString()
String content

Put (HttpUtils)
String contentType
String text
def userName
def password

String getText(){
def conn = new URL(url).openConnection()
conn.setRequestMethod("PUT" )
conn.setRequestProperty("Content-Type" , contentType)
conn.doOutput = true
conn.doInput = true

if (userName && password) {


conn.setRequestProperty("Authorization", "Basic ${userName}:${password}")
}

conn.outputStream.withWriter { out ->


out.write(queryString.toString())
out.flush()
out.close()
}

def response
if (conn.HTTP_OK == conn?.responseCode) {
response = conn.content.text
} else {
response = "URL: " + this.toString() + "\n" +
"RESPONSE CODE: " + responseCode
}
conn.disconnect()
return response
}

String toString(){
return url + "?" + queryString.toString()
}
}
RESTFul WebServices
class UserInfoController {
def index = { redirect(action:show,params:params) }

def show = {
def result = session.user
format(result)
}

def beforeInterceptor = {

def authHeader = request.getHeader("Authorization")


if (authHeader) {
def tokens = authHeader.split(' ')
def user_password = tokens[1]
tokens = user_password.split(':')
def userid = tokens[0]
def password = tokens[1]
// At this point, the userid and password could be verified
// to make sure that the person making the request is authenticated
//
// << AUTHENTICATION LOGIC / CALL >>
//
// Put look up the user object and put it into session for use
// later by the controllers.
def user = User.findByUserName(userid)
if (user) {
session.user = user
} else {
session.user = null
}
}
}

private format(obj) {
def restType = (params.rest == "rest")?"XML":"JSON"
println obj."encodeAs$restType"()
render obj."encodeAs$restType"()
}

}
class UserInfoController {
def index = { redirect(action:show,params:params) }

def show = {
def result = session.user
format(result)
}

def beforeInterceptor = {

def authHeader = request.getHeader("Authorization")


if (authHeader) {
def tokens = authHeader.split(' ')
def user_password = tokens[1]
tokens = user_password.split(':')
def userid = tokens[0]
def password = tokens[1]
// At this point, the userid and password could be verified
// to make sure that the person making the request is authenticated
//
// << AUTHENTICATION LOGIC / CALL >>
//
// Put look up the user object and put it into session for use
// later by the controllers.
def user = User.findByUserName(userid)
if (user) {
session.user = user
} else {
session.user = null
}
}
}

private format(obj) {
def restType = (params.rest == "rest")?"XML":"JSON"
println obj."encodeAs$restType"()
render obj."encodeAs$restType"()
}

}
Other Griffon Apps
http://github.com/jshingler/gcollabtodo/tree/master
Resources
Introduction to Groovy
Groovy Basics
More Advanced Groovy
Introduction to Grails
Building the User Interface
Building Domains and Services
Security in Grails
Web 2.0Ajax and Friends
Web Services
Reporting
Batch Processing
Deploying and Upgrading
Alternative Clients
Resources
Griffon
griffon.codehause.org
griffon-user@groovy.codehause.org
Grails
www.grails.org
Coming
Books Soon
Resources
SwingLabs
swinglabs.org
MigLayout
miglayout.org
GlazedLists
publicobject.com/glazedlists
Conclusion
Thank You for your time

Blog:
http://jshingler.blogspot.com

Email:
ShinglerJim@gmail.com

LinkedIn:
http://www.linkedin.com/in/jimshingler

Twitter:
http://www.twitter.com/jshingler

You might also like