Upload
james-williams
View
4.207
Download
0
Tags:
Embed Size (px)
DESCRIPTION
Introduction to Griffon WorkshopPresented at GR8Conf USAon 27 June 2011
Citation preview
Introduction to Griffon
James Williams
About Me
● Co-founder of Griffon
● Author of "Learning HTML5 Game Programming"
http://amzn.to/HTML5-Game-Book
● Blog: http://jameswilliams.be
● Twitter: @ecspike
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
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!
Common Griffon Commands
● create-app
● create-mvc
● test-app
● run-app
● help
● package
● Form: griffon <environment> command <options>
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.
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)
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}
Code
cd <GRIFFON INSTALL DIR>
cd samples/SwingPad
griffon app
Common Widgets
● label
● button
● checkBox, radioButton
● textField
● textArea
● panel
● hbox, vbox
Code
import javax.swing.JOptionPane
button('Click', actionPerformed: {JOptionPane.showMessageDialog( null, "Button clicked at ${new Date()}" )})
Crash course in MigLayout
● Grid-based LayoutManager
● Human-readable
● Docking (BorderLayout)
● Absolute Positioning
● Constraints
● Units (px, mm, cm, pts, in, %)
Crash course in MigLayout
● wrap, newline
● w/width, h/height
● Docking (BorderLayout)
● gap
● span
● cell [column] [row]
Code
import net.miginfocom.swing.MigLayout
panel(layout:new MigLayout()) {label(text:'label')label(text:'Cell 1 1 ', constraints:'cell 1 1')}
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')}
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)
Sample Griffon Model file
import groovy.beans.Bindable
class HelloModel { // @Bindable String propName}
Binding Forms
textField(text:bind{model.property})
textField(text:bind(source:model, sourceProperty:"data"))
textField(text:bind(target:model, targetProperty:"data"))
Griffon Controllers
● Brains of the MVC triad
● Contain actions for the triad
● Injected with references to the model and view
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 // }
}
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
Code
(From inside a Griffon app directory)
griffon list-pluginsgriffon lP
griffon plugin-info <name of plugin>
Creating A ToDo Application
Code
griffon create-app TodoApp
<wait... wait... wait...>
griffon install-plugin swingx-buildergriffon install-plugin swingxtras-buildergriffon install-plugin validation
Code (/src/main/todoapp/...)
class TodoItem {String todoTextDate dueDateList <String>tags Boolean isDoneBoolean isSavedpublic tagsToString() {Arrays.sort(tags)tags.join(',')}}
Code (/griffon-app/models/todoapp/.)
import groovy.beans.Bindable
class TodoModel {@Bindable String todoText@Bindable Date dueDate@Bindable tagsTextList <TodoItem> todos }
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')
}
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')}
Swing isn't slow, ...
... devs are just coding it badly
Don't do this!
JButton b = new JButton("Run query");b.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { runQueries(); }});
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
(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)
Code (griffon-app/controllers/...)
def load = {doOutside {def todos = model.derby.all()todos.each {def todo = TodoItem.fromMap(it)model.todos.add(todo)}}}
Adding a table of todo items
The old way ...
● A very brittle TableModel
● Hard to update and sort items
● Hard to map between POJOs and rows
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)}
Code (griffon-app/views/todoapp/...)
...jxtitledPanel(title:'Tasks') {scrollPane {jxtable(id:'table', model:model.todoTableModel)}}
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
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
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
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)
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)
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)
Adding a bit of Swing glue:Table Editors and Renderers
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
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
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}}
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()}}
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())
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)
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() }
Saving to disk
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
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
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
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"})
Validation
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
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)}}
Supported Validators
● blank
● creditCard
● inetAddress
● inList
● matches
● max
● maxSize
● min
● minSize
● notEqual
● nullable
● range
● size
● url
● validator
Validation an object
def addItem = {if (model.validate()) {// save file } catch(Exception ex) { }
} else {model.errors.each{error ->println error}}}
Showing a error component
jxtextField( id:'todoField', columns:45, text:bind(target:model, 'todoText'), constraints:'w 90%', errorRenderer:'for: todoText, styles: [highlight, popup]')
Printing in Griffon
There once was a java.net project called EasyPrint
It was a casualty to the Java.net/Kenai conversion
:(
From HTML to PDF
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())}}}}}
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.") } }
Questions ?