Upload
jim-shingler
View
14.377
Download
0
Tags:
Embed Size (px)
Citation preview
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
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.0—Ajax and Friends• Web Services• Reporting• Batch Processing• Deploying and Upgrading• Alternative Clients
Resources• Griffon
• griffon.codehause.org• [email protected]
• Grails• www.grails.org
• BooksComing
Soon
Resources• SwingLabs
• swinglabs.org• MigLayout
• miglayout.org• GlazedLists
• publicobject.com/glazedlists
Conclusion
• Blog: http://jshingler.blogspot.com
• Email: [email protected]
• LinkedIn: http://www.linkedin.com/in/jimshingler
• Twitter: http://www.twitter.com/jshingler
Thank You for your time