Geertjan is a DZone Zone Leader and has posted 466 posts at DZone. You can read more from them at their website. View Full User Profile

Flying with Griffon

09.12.2008
| 23052 views |
  • submit to reddit

New frameworks come and go. They tend to stand and fall based on whether people start experimenting with them. The new Griffon framework is unlikely to fall any time soon, since the large and vibrant Groovy/Grails community has a vested interest in it.

However, to do my bit, I'm going to start playing with it myself. The Griffon creators were smart enough to include some samples with their intial distribution and so the first application I've made is a small subset of one of these samples. Though it is small, many of the basic Griffon concepts are touched on and the end result is no different to any other Swing application. It is a JXTree inside a JScrollPane within a JPanel, with a JToolBar containing two buttons that expand/collapse tree nodes containing the text of the nodes in the NetBeans Javadoc:

However, I also have an applet (and a JNLP application), created via "griffon run-app", i.e., the same Griffon command that created the above Swing application. The applet behaves identically to the Swing application, so that the 'Collapse all' and 'Expand all' buttons do what you would expect them to do, with the general appearance of the applet being identical to the Swing application:

Note: For purposes of this simple example, I didn't implement a TreeSelectionListener, so nothing happens when you click any of the leaf nodes above.

Let's first look at all the code, before putting everything together into an application. Since Griffon encourages an MVC structure, the code is going to be split into those three parts.

The View

We begin with a simple view that does nothing:

application( title: "Griffon Demo", size: [250,300], locationByPlatform: true ) {
panel( ) {
borderLayout()
scrollPane( constraints: CENTER ) {
jxtree( id: "topics" )
}
toolBar( constraints: SOUTH ) {
button( )
button( )
}
}
}

The above is the complete content (i.e., there's nothing more than that) in a Groovy file called "GriffonDemoView". Why do we assign an id to the JXTree? So that we can refer to it from the controller! That's where we'll get the content of the JXTree and then pass it into the view. The Groovy code is such that one should be able to read the above with the naked eye and then immediately visualize what the user interface will consist of. However, we want our two buttons to be able to do something. Therefore, we will have a separate Groovy file, in this case called "GriffonDemoActions", where all our actions will be found. Then we'll hook them into the view. Here's the complete content of a separate Groovy file, where all our actions will be defined:

actions {

action( id: 'collapseAllAction',
name: "Collapse all",
closure: controller.collapseAll,
accelerator: shortcut('C'),
mnemonic: 'C',
shortDescription: "Collapse all categories",
smallIcon: imageIcon("org/tango-project/tango-icon-theme/16x16/actions/go-first.png")
)

action( id: 'expandAllAction',
name: "Expand all",
closure: controller.expandAll,
accelerator: shortcut('E'),
mnemonic: 'E',
shortDescription: "Expand all categories",
smallIcon: imageIcon("org/tango-project/tango-icon-theme/16x16/actions/go-last.png")
)

}

Everything above is how you'd expect it to be, except for each action's "closure" property. There you see a reference to a closure defined in the controller. That's where the real work is done by each action. We'll look at that part when we deal with the controller. For now, we'll hook the actions into the buttons:

build(GriffonDemoActions)

application( title: "Griffon Demo", size: [250,300], locationByPlatform: true ) {
panel( ) {
borderLayout()
scrollPane( constraints: CENTER ) {
jxtree( id: "topics" )
}
toolBar( constraints: SOUTH ) {
button( action: collapseAllAction )
button( action: expandAllAction )
}
}
}

Take note of line 1 above, which includes the Groovy file "GriffonDemoActions", so that our two buttons can refer to the actions we defined in that file. Currently the Actions file needs to be wired explicitly, but possibly in the future "GroovyDemoActions" would automatically be included, Andres tells me.

The Controller

Next, the controller.

import javax.swing.tree.*

class GriffonDemoController {

def model
def view

def expandAll = { evt ->
ViewUtils.expandTree( view.topics )
}

def collapseAll = { evt ->
ViewUtils.collapseTree( view.topics )
}


def loadPages() {
doOutside {
def contents = new DefaultMutableTreeNode("NetBeans Javadoc")
def leafNodes = new URL(model.menuUrl).text
def lastCategory = null
(leafNodes =~ /href="(([a-zA-Z-]+)\/(.+?)\.html)"/).each { match ->
def category = new DefaultMutableTreeNode(match[2])
def pageNode = new PageNode( title: match[3] )
if( lastCategory?.toString() == category.toString() ){
lastCategory.add( new DefaultMutableTreeNode(pageNode) )
}else{
lastCategory = category
category.add( new DefaultMutableTreeNode(pageNode) )
contents.add( category )
}
}
doLater {
view.topics.model = new DefaultTreeModel(contents)
}
}
}

}

Let's take a look at what's going on here. Lines 9 and 13 refer to a utility class that I copied, together with the PageNode file (which is a simple POGO) from the GrailsSnoop sample (which is part of the Griffon distribution). I'm hoping the whole ViewUtils class will be part of the API and I've written to Andres about this. Notice how it is wired into the view, via the "topics" id of the JXTree. Similarly, line 34 fills the JXTree's model with the content that is built from line 18 to 32. (Look at line 20 to see a reference to the model, where the URL to the location that will be parsed is set.)

And when is the "loadPages()" method called? From one of the lifecycle files that is automatically created when you run "griffon create-app"; this particular one is called "Startup.groovy":
def rootController = app.controllers.root
rootController.loadPages()

Hence, the entry point to your application is in the controller, via the lifecycle files that the "griffon create-app" creates for you. But, how does Griffon know that "app.controllers.root" is "GriffonDemoController"? For that purpose, several configuration files are found in "griffon-app/conf", populated during the running of "griffon create-app". One of these, called "Application.groovy", marks out our generated files as follows:

mvcGroups {
root {
model = 'GriffonDemoModel'
view = 'GriffonDemoView'
controller = 'GriffonDemoController'
}
}

In other words, not only does Griffon explicitly handle your lifecycle, but it also generates skeletons for the requisite classes for you.

The Model

Finally, the model. In this case, it's really simple:

class GriffonDemoModel {
String baseUrl = "http://bits.netbeans.org/dev/javadoc/"
String menuUrl = baseUrl + "allclasses-frame.html"
}

And that's it! The application is complete. Here's the structure once everything above is put together:

Of the files listed above, the ONLY one you had to create manually was "GriffonDemoActions", though even that could be included in the "create-app" script in the future. Now run "griffon run-app" and you'll have a Swing application, an applet, and a JNLP application. As shown at the start of this article, your application will list the content of the NetBeans Javadoc. Pretty cool, I reckon.

AttachmentSize
fig-1.png25.08 KB
fig-2.png52.17 KB
fig-3.png50.52 KB
Published at DZone with permission of its author, Geertjan Wielenga.

Comments

Mike Burke replied on Sat, 2008/09/13 - 10:07am

Geertjan,

This is an excellent introduction to the architecture of Griffon. Where could I find some more information? The available documentation seems a bit thin.

Geertjan Wielenga replied on Mon, 2008/09/15 - 2:59pm

Hi mcburke, I'm also looking forward to more documentation, but I'm sure it'll be there once more releases are made available. In the meantime, the samples are very informative. I'd say, start playing with them and extrapolate your own principles from their commonalities.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.