Introduction to Griffon
Upcoming SlideShare
Loading in...5
×
 

Introduction to Griffon

on

  • 4,027 views

Introduction to Griffon Workshop

Introduction to Griffon Workshop
Presented at GR8Conf USA
on 27 June 2011

Statistics

Views

Total Views
4,027
Views on SlideShare
3,993
Embed Views
34

Actions

Likes
3
Downloads
51
Comments
0

5 Embeds 34

http://lanyrd.com 23
http://www.slideshare.net 8
http://next1.outlawreg.kodingen.com 1
http://paper.li 1
http://www.pearltrees.com 1

Accessibility

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

Introduction to Griffon Introduction to Griffon Presentation Transcript

  • 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 codeCode 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 => pYou 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 Filepackage helloapplication(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}
  • Codecd <GRIFFON INSTALL DIR>cd samples/SwingPadgriffon app
  • Common Widgets● label● button● checkBox, radioButton● textField● textArea● panel● hbox, vbox
  • Codeimport javax.swing.JOptionPanebutton(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]
  • Codeimport net.miginfocom.swing.MigLayoutpanel(layout:new MigLayout()) {label(text:label)label(text:Cell 1 1 , constraints:cell 1 1)}
  • Codeimport net.miginfocom.swing.MigLayoutpanel(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 fileimport groovy.beans.Bindableclass HelloModel { // @Bindable String propName}
  • Binding FormstextField(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 Fileclass 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 lPgriffon plugin-info <name of plugin>
  • Creating A ToDo Application
  • Codegriffon 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.Bindableclass 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 isnt slow, ...
  • ... devs are just coding it badly
  • Dont 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 Component Boolean* 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 objects toString() value
  • Code (src/main/groovy)class CheckBoxRenderer extends JCheckBox implements TableCellRenderer {public Component getTableCellRendererComponent(JTable table, Objectvalue, 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(newJCheckBox()))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 arent exactly pinned down yet
  • Code// from modeldef derby = new Deckchair([name:todos, adaptor:derby])//loading todosdef 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 ● min● creditCard ● minSize● email ● notEqual● inetAddress ● nullable● inList ● range● matches ● size● max ● url
  • Validation an objectdef addItem = {if (model.validate()) {// save file} catch(Exception ex) { }} else {model.errors.each{error ->println error}}}
  • Showing a error componentjxtextField( 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 ?