Introduction to Griffon

74
Introduction to Griffon James Williams

description

Introduction to Griffon WorkshopPresented at GR8Conf USAon 27 June 2011

Transcript of Introduction to Griffon

Page 1: Introduction to Griffon

Introduction to Griffon

James Williams

Page 2: Introduction to Griffon

About Me

● Co-founder of Griffon

● Author of "Learning HTML5 Game Programming"

http://amzn.to/HTML5-Game-Book

● Blog: http://jameswilliams.be

● Twitter: @ecspike

Page 3: Introduction to Griffon

Agenda

● What is Griffon?● Common Griffon Commands● Model - View - Controller● MigLayout● Binding● Plugins● Validation● Lots of code

Code for this demo: https://github.com/jwill/Conference-Demos

Page 4: Introduction to Griffon

What is Griffon?

● Apache 2 Licensed

● http://griffon.codehaus.org

● Inspired by Grails and the Swing Application Framework

● Extensible with plugins and addons

● Deployable to Webstart, Applet, or single jar file

● Now to Github!

Page 5: Introduction to Griffon

Common Griffon Commands

● create-app

● create-mvc

● test-app

● run-app

● help

● package

● Form: griffon <environment> command <options>

Page 6: Introduction to Griffon

Griffon Aliases

● create-app

● create-mvc => cM

● test-app => tA

● run-app => app

● help => h

● package => p

You can even create your own with the create-alias command.

Page 7: Introduction to Griffon

Griffon Views

● Represent the presentation layer of the MVC triad

● Use Domain Specific Languages called UI Builders

● SwingBuilder is bundled

● Additional builder available as plugins (SwingXBuilder, JIDE, MacWidgets, etc)

Page 8: Introduction to Griffon

Sample Griffon View File

package hello

application(title: 'Hello', preferredSize: [320, 240], pack: true, //location: [50,50], locationByPlatform:true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [/* truncated */]) { // add content here label('Content Goes Here') // delete me}

Page 9: Introduction to Griffon

Code

cd <GRIFFON INSTALL DIR>

cd samples/SwingPad

griffon app

Page 10: Introduction to Griffon

Common Widgets

● label

● button

● checkBox, radioButton

● textField

● textArea

● panel

● hbox, vbox

Page 11: Introduction to Griffon

Code

import javax.swing.JOptionPane

button('Click', actionPerformed: {JOptionPane.showMessageDialog( null, "Button clicked at ${new Date()}" )})

Page 12: Introduction to Griffon

Crash course in MigLayout

● Grid-based LayoutManager

● Human-readable

● Docking (BorderLayout)

● Absolute Positioning

● Constraints

● Units (px, mm, cm, pts, in, %)

Page 13: Introduction to Griffon

Crash course in MigLayout

● wrap, newline

● w/width, h/height

● Docking (BorderLayout)

● gap

● span

● cell [column] [row]

Page 14: Introduction to Griffon

Code

import net.miginfocom.swing.MigLayout

panel(layout:new MigLayout()) {label(text:'label')label(text:'Cell 1 1 ', constraints:'cell 1 1')}

Page 15: Introduction to Griffon

Code

import net.miginfocom.swing.MigLayout

panel(layout:new MigLayout()) {label(text:'Text', constraints:'wrap')label(text:'Text2 ')

label(text:'Text3', constraints:'w 50px, newline')label(text:'Text4 ')

textField(columns:10, constraints:'span 2, newline')}

Page 16: Introduction to Griffon

Griffon Models

● Hold data for the MVC triad

● Can send/receive(@Bindable) data to/from widgets in the view

● Can do Grails-style validation (plugin)

Page 17: Introduction to Griffon

Sample Griffon Model file

import groovy.beans.Bindable

class HelloModel { // @Bindable String propName}

Page 18: Introduction to Griffon

Binding Forms

textField(text:bind{model.property})

textField(text:bind(source:model, sourceProperty:"data"))

textField(text:bind(target:model, targetProperty:"data"))

Page 19: Introduction to Griffon

Griffon Controllers

● Brains of the MVC triad

● Contain actions for the triad

● Injected with references to the model and view

Page 20: Introduction to Griffon

Sample Griffon Controller File

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

// void mvcGroupInit(Map args) { // // this method is called after model and view are injected // }

// void mvcGroupDestroy() { // // this method is called when the group is destroyed // }

}

Page 21: Introduction to Griffon

Griffon Plugins

● Extend app functionality at runtime or compile-time

● Can be as simple as adding a couple jars...

● Or as complex as creating a custom builder to use them

● Common areas:

○ UI Toolkits

○ Dependency Injection

○ Persistence

Page 22: Introduction to Griffon

Code

(From inside a Griffon app directory)

griffon list-pluginsgriffon lP

griffon plugin-info <name of plugin>

Page 23: Introduction to Griffon

Creating A ToDo Application

Page 24: Introduction to Griffon

Code

griffon create-app TodoApp

<wait... wait... wait...>

griffon install-plugin swingx-buildergriffon install-plugin swingxtras-buildergriffon install-plugin validation

Page 25: Introduction to Griffon

Code (/src/main/todoapp/...)

class TodoItem {String todoTextDate dueDateList <String>tags Boolean isDoneBoolean isSavedpublic tagsToString() {Arrays.sort(tags)tags.join(',')}}

Page 26: Introduction to Griffon

Code (/griffon-app/models/todoapp/.)

import groovy.beans.Bindable

class TodoModel {@Bindable String todoText@Bindable Date dueDate@Bindable tagsTextList <TodoItem> todos }

Page 27: Introduction to Griffon

Code (griffon-app/views/todoapp/...)

size: [640,480],locationByPlatform:true, layout: new MigLayout()) { // Add Todo field jxtextField(id:'todoField', columns:45, text:bind(target:model, 'todoText'), constraints:'w 90%') promptSupport(todoField, prompt: "Add Todo, type here") button(id:'btnAdd', text:'Add', constraints:'w 10%, wrap')

}

Page 28: Introduction to Griffon
Page 29: Introduction to Griffon

Code (griffon-app/views/todoapp/...)

// More options (date, tags, etc)jxtaskPane(title:'More Options', constraints: 'w 100%, spanx 2, wrap', layout:new MigLayout()) {label(text:'Due Date:', constraints:'wrap')jxdatePicker(id:'datePicker', date:bind(target:model,'dueDate'), constraints:'wrap')label(text:'Tags', constraints:'wrap')textField(id:'tagsField', columns:40, text:bind(target:model, 'tagsText'))promptSupport(tagsField, prompt:'Enter tags comma separated')}

Page 30: Introduction to Griffon
Page 31: Introduction to Griffon

Swing isn't slow, ...

Page 32: Introduction to Griffon

... devs are just coding it badly

Page 33: Introduction to Griffon

Don't do this!

JButton b = new JButton("Run query");b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { runQueries(); }});

Page 34: Introduction to Griffon

Threading in Swing

● All updates to the UI must happen on the single Event Dispatcher Thread(EDT)

● Nothing else should be executed on the EDT*

● Bad dev starts long running task, UI locks up until it is done

● SwingUtilities provides the following functions:

○ isEventDispatchThread()○ invokeLater(Runnable),

invoke Runnable when convenient○ invokeAndWait(Runnable)

invoke when convenient and wait for completion

Page 35: Introduction to Griffon

(Swing) Threading in Griffon

● Encapsulates SwingUtilities and simple threading

● Adds closure for running a task outside the EDT

● Available closures:○ doLater { ... } ==> SwingUtilities.doLater○ edt{ ...} ==> SwingUtilities.invokeAndWait○ doOutside{..} ==> Spawns new Thread if inside EDT

● Griffon 0.9.2+ encloses controller actions with doOutside by

default (can be turned off)

Page 36: Introduction to Griffon

Code (griffon-app/controllers/...)

def load = {doOutside {def todos = model.derby.all()todos.each {def todo = TodoItem.fromMap(it)model.todos.add(todo)}}}

Page 37: Introduction to Griffon

Adding a table of todo items

Page 38: Introduction to Griffon

The old way ...

● A very brittle TableModel

● Hard to update and sort items

● Hard to map between POJOs and rows

Page 39: Introduction to Griffon

Code (SwingPad)

import javax.swing.table.DefaultTableModeldef a = new DefaultTableModel(['A','B','C'] as String[], 0)a.addRow(1,2,3)a.addRow(4,5,6)a.addRow(7,8,9)

scrollPane {jxtable(model:a)}

Page 40: Introduction to Griffon

Code (griffon-app/views/todoapp/...)

...jxtitledPanel(title:'Tasks') {scrollPane {jxtable(id:'table', model:model.todoTableModel)}}

Page 41: Introduction to Griffon

GlazedLists

● Easy data model management for:○ JComboBox,○ JList ○ JTable

● Provides easy dynamic sorting and filtering

● Supports both SWT and Swing

● Supports concurrency

● Link: http://glazedlists.com

Page 42: Introduction to Griffon

BasicEventList

● Compatible with ArrayList and Vector

● Stores the items for your List or Table

● Parameterized around POJO BasicEventList todos = new BasicEventList<TodoItem>()

● Adds listeners for changes in the list

Page 43: Introduction to Griffon

BasicEvent<Widget>Model

● Receives changes from BasicEventList and pushes them to the widget

● BasicEventListModel and BasicEventComboModel take only a BasicEventList (POJOs to display) as a parameter

● JTables require a bit more definition

Page 44: Introduction to Griffon

TableFormat

● Interface that defines how the table is displayed in the GUI

● By default is not editable after initialization

● Required functions:○ getColumnCount()○ getColumnName(int)○ getColumnValue(obj, int)

Page 45: Introduction to Griffon

WritableTableFormat

● Interface allowing editing of fields

● Can set granular edit policy with isEditable

● Uses default field editor (usually a JTextField)

● Required functions:○ isEditable(obj, column)○ setColumnValue(obj, value, column)

Page 46: Introduction to Griffon

AdvancedTableFormat

● Interface that allows custom cell renderers For example: DatePickers, Check boxes, Sliders, etc.

● JTables set policies on how to render a cell with a certain class type

● JXTables do sorting and filtering so a separate comparator is not needed

● Required functions:○ getColumnClass(int)○ getColumnComparator(int)

Page 47: Introduction to Griffon

Adding a bit of Swing glue:Table Editors and Renderers

Page 48: Introduction to Griffon

TableEditor vs TableRenderers

● Any Swing component can be an editor or renderer

● TableEditors show when that cell is being edited

● TableRenderers appear in all other cases

● Can be mixed an matched For example: a default renderer but a specialized renderer

Page 49: Introduction to Griffon

Default TableRenderers

Class Renderer ComponentBoolean* JCheckBox*Number JLabel, right-aligned

Double, Float JLabel, right-aligned with coersion to String using NumberFormat

Date JLabel, with coersion to String using DateFormat

Object JLabel with object's toString() value

Page 50: Introduction to Griffon

Code (src/main/groovy)

class CheckBoxRenderer extends JCheckBox implements TableCellRenderer {public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) {if (isSelected) {setForeground(table.getSelectionForeground())setBackground(table.getSelectionBackground())} else {setForeground(table.getForeground())setBackground(table.getBackground())}setSelected(value)return this}}

Page 51: Introduction to Griffon

Code (src/main/groovy)

class DatePickerEditor extends AbstractCellEditor implements TableCellEditor {def component = new JXDatePicker()public Component getTableCellEditorComponent(JTable table, Object value, boolean isSelected, int row, int column) {component.setDate(value)return component}public Object getCellEditorValue() {return component.getDate()}}

Page 52: Introduction to Griffon

Code (griffon-app/controllers/...)

// setup renderers and editorsview.table.setDefaultRenderer(Boolean.class, new CheckBoxRenderer())view.table.setDefaultEditor(Boolean.class, new DefaultCellEditor(new JCheckBox()))

view.table.setDefaultEditor(Date.class, new DatePickerEditor())

Page 53: Introduction to Griffon

Code (griffon-app/controllers/...)

// table model listenermodel.todoTableModel.addTableModelListener([tableChanged:{evt ->def i = evt.getFirstRow()def j = evt.getLastRow()if (i == j && evt.getType() == TableModelEvent.UPDATE) {// do something with the update date}}] as TableModelListener)

Page 54: Introduction to Griffon

Code (griffon-app/controllers/...)

def deleteCompleted = { def lock = model.todos.getReadWriteLock().writeLock() lock.lock() def list = model.todos.findAll{item -> item.isDone == true } list.each { item -> model.derby.remove(item.id) model.todos.remove(item) } lock.unlock() }

Page 55: Introduction to Griffon
Page 56: Introduction to Griffon

Saving to disk

Page 57: Introduction to Griffon

Introducing Deckchair

● Modeled after Lawnchair, a lightweight JSON store (JS)

● Written in Groovy

● Uses an adapter-implementation model for persistence Derby is the only implemented backend for Deckchair

● Provides more flexibility than direct use of backends

● Link: http://github.com/jwill/deckchair

Page 58: Introduction to Griffon

Deckchair API

● save, saves a single object

● get, retreives an object

● each, executes code for every item in the result set

● find, finds objects using a closure

● all, returns all objects in the set

● nuke, destroys all objects in the set

● remove, destroys a single object

Page 59: Introduction to Griffon

Deckchair Derby Internals

● Creates a table with:○ id, 36-character VARCHAR○ timestamp, BIGINT○ value, LONG VARCHAR (about 32,000 characters)

● Objects are serialized to JSON strings on insertion

● Only the uniqueness of the id field is enforced

● Good for small amounts of data and demos

● Good for data models that aren't exactly pinned down yet

Page 60: Introduction to Griffon

Code

// from modeldef derby = new Deckchair([name:'todos', adaptor:'derby'])

//loading todos def todos = model.derby.all()

// save function with optional closure to print status after completionmodel.derby.save(changedTodo.toMap(), {obj -> println obj; println "saved todo"})

Page 61: Introduction to Griffon

Validation

Page 62: Introduction to Griffon

GValidation Plugin

● Provides Grails style validation and messages

● Uses a Grails-like DSL

● Supports custom constraints in addition to built-in types

● Provides visual cues for incorrect data

● Can internationalize error messages with the Griffon i18n plugin

Page 63: Introduction to Griffon

Code (griffon-app/models/...)

package todoimport jwill.deckchair.*

import groovy.beans.Bindable//...

@Validatableclass TodoModel {@Bindable String todoText@Bindable Date dueDate@Bindable tagsText//...static constraints = {todoText(blank:false)dueDate(nullable:true)tagsText(blank:true)}}

Page 64: Introduction to Griffon

Supported Validators

● blank

● creditCard

● email

● inetAddress

● inList

● matches

● max

● maxSize

● min

● minSize

● notEqual

● nullable

● range

● size

● url

● validator

Page 65: Introduction to Griffon

Validation an object

def addItem = {if (model.validate()) {// save file } catch(Exception ex) { }

} else {model.errors.each{error ->println error}}}

Page 66: Introduction to Griffon

Showing a error component

jxtextField( id:'todoField', columns:45, text:bind(target:model, 'todoText'), constraints:'w 90%', errorRenderer:'for: todoText, styles: [highlight, popup]')

Page 67: Introduction to Griffon
Page 68: Introduction to Griffon

Printing in Griffon

Page 69: Introduction to Griffon

There once was a java.net project called EasyPrint

Page 70: Introduction to Griffon

It was a casualty to the Java.net/Kenai conversion

:(

Page 71: Introduction to Griffon

From HTML to PDF

Page 72: Introduction to Griffon

Code (griffon-app/controllers/...)

def builder = new MarkupBuilder(writer)

builder.html {head{title('My Todo List')}body {h2("My Todo List")br {}table (width:'90%', border:1, 'border-spacing':0){tr { td('Completed'); td('Description'); td('Due Date'); td('Tags') }model.todos.each { item ->tr {if (item.isDone) { td('Done') } else { td() }td(item.todoText)td(item.dueDate)td(item.tagsToString())}}}}}

Page 73: Introduction to Griffon

Code (griffon-app/controllers/...)

def createPDF = { createHTML() def file = File.createTempFile("todos","txt"); file.write(writer.toString()) def docBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder() def doc = docBuilder.parse(file) def renderer = new ITextRenderer() renderer.setDocument(doc, null) def outputFile = "todos.pdf" def os = new FileOutputStream(outputFile) renderer.layout(); renderer.createPDF(os); os.close() edt { JOptionPane.showMessageDialog(null, "PDF created.") } }

Page 74: Introduction to Griffon

Questions ?