Griffon In Front Grails In Back

Post on 17-May-2015

14,377 views 0 download

Tags:

transcript

Griffon in FrontGrails in Back

Leveraging Grails with Griffon

Griffon in FrontGrails in Back

Leveraging Grails with Griffon

AbstractGroovy 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.

IntroductionMy name is Jim Shingler

Chief Technical Architect

President of Genuine Solutions

Beginning Groovy and GrailsCo-Author

FallME (Inversion of Control for JavaME)Co-Founder

Griffon Founders

Griffon Founders

Danno Ferrinhttp://shemnon.com/speling

Andres Almirayhttp://jroller.com/aalmiray

James Williamshttp://jameswilliams.be/blog

Common Complaints about Swing

Its hardHave to code too muchCode is complexNot many Advanced GUI Components (Myth)

Start Small

• Swing and SwingX Builder

• GUI Components

• About Box

• Define and Process Actions

BuildersThe 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”

• SwingXBuilderExtends SwingBuilder adding the SwingLabs Components

• JideBuilderExtends SwingBuilder adding JIDE Components

• SWTBuilderApplies the Builder Pattern to the construction of SWT“Makes SWT Groovy”

PluginsA 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.

Griffon’s plugin community has just begun but it is growing fast.

See: >griffon list-plugins

http://grails.org/Pluginshttp://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

@Bindableclass Sample2Model { def count = 0}

Viewapplication(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)}

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

glue() menu(text: 'Help', mnemonic: 'H') { menuItem(aboutAction) }}

MenuBar

Actions// create the actionsaction(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

GoalMenu Bar

Tool Bar

Content Area

Status Bar

GoalMenu Bar

Tool Bar

Content Area

Status Bar

Tips Dialog

Login Dialog

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

• Table and Fields

• Wire it together with Actions

• Make calls to Web Services

Glazed Lists

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

Glazed Lists

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

Glazed Lists

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

Glazed Lists

OrganizationController View

Model

MVC Triad

OrganizationController View

Model

MVC Triad

Griffon Framework

OrganizationController View

Model

Griffon Framework

OrganizationController View

Menu Bar

Model

Griffon Framework

OrganizationController View

Menu Bar

About Dialog

Model

Griffon Framework

OrganizationController View

Menu Bar

Content Pane

About Dialog

Model

Griffon Framework

OrganizationController View

Menu Bar

Content Pane

Tool Bar

About Dialog

Model

Griffon Framework

OrganizationController View

Menu Bar

Content Pane

Tool Bar

Status Bar

About Dialog

Model

Griffon Framework

OrganizationController View

Menu Bar

Content Pane

Tool Bar

Status Bar

About Dialog

Model

Griffon Framework

Services

OrganizationController

Http UtilsGetPutPost

Delete

ViewMenu Bar

Content Pane

Tool Bar

Status Bar

About Dialog

Model

Griffon Framework

Services

OrganizationController

Http UtilsGetPutPost

Delete

Rest Controller

ViewMenu Bar

Content Pane

Tool Bar

Status Bar

About Dialog

Model

Griffon Framework

Services

Interaction with RESTful WebServicesAction SQL

MethodHTTP

MethodGrails

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 }

Let’s Look at the Code

package http.utils

class Get{ String url QueryString queryString = new QueryString() String text 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() }}

Get (HttpUtils)

Save Todo void saveTodo(event) { fillSelectedTodoFromView() def todo = selectedTodo()

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 Savestatic 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.utilsclass Put{ String url QueryString queryString = new QueryString() String content 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() }}

Put (HttpUtils)

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

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.0—Ajax 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

• BooksComing

Soon

Resources• SwingLabs

• swinglabs.org• MigLayout

• miglayout.org• GlazedLists

• publicobject.com/glazedlists

Conclusion

• Blog: http://jshingler.blogspot.com

• Email: ShinglerJim@gmail.com

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

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

Thank You for your time