5 Application Overview - Reference Documentation
Authors: Andres Almiray
Version: 1.2.0
Table of Contents
5 Application Overview
5.1 Directory Structure
Here's a more detailed explanation of each directory within the application's structuregriffon-app
- top level directory for Groovy sources.conf
- Configuration sources.webstart
- Webstart resources.keys
- Jar signing keys.dist
- Package specific files.shared
- Common files to all packaging targets (like LICENSE.txt)metainf
- Files that should go in META-INF inside the application/addon jar.models
- Models.views
- Views.controllers
- Controllers.services
- Services.resources
- Images, properties files, etc.i18n
- Support for internationalization (i18n).scripts
- Gant scripts.src
- Supporting sources.main
- Other Groovy/Java sources.test
- Unit and integration tests.unit
- Directory for unit tests.integration
- Directory for integration tests.cli
- Directory for command line tests (Scripts).
5.2 The MVC Pattern
All Griffon applications operate with a basic unit called the MVC group. An MVC group is comprised of 3 member parts: Models, Views and Controllers. However it is possible to add (or even remove) members from an MVC group by carefully choosing a suitable configuration.MVC groups configuration is setup inApplication.groovy
located inside griffon-app/conf
. This file holds an entry for every MVC group that the application has (not counting those provided by plugins/addons).Here's how a typical MVC group configuration looks likemvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' } }
model
and view
will be initialized before the controller
. Do not mistake initialization for instantiation, as initialization relies on calling mvcGroupInit on the group member.MVC group configurations accept a special key that defines additional configuration for that group, as it can be seen in the following snippetmvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' } // MVC Group for "foo" 'foo' { model = 'sample.FooModel' view = 'sample.FooView' controller = 'sample.FooController' config { key = 'bar' } } }
FooController
can reach the key defined in the configurationclass FooController { void mvcGroupInit(Map args) { println args.configuration.config.key } }
5.2.1 MVCGroupManager
This class is responsible for holding the configuration of all MVC groups no matter how they were defined, which can be either inApplication.groovy
or in an addon descriptor.During the startup sequence an instance of MVCGroupManager
will be created and initialized. Later the application will instruct this instance to create all startup groups as required. MVCGroupManager
has a handful set of methods that deal with MVC group configuration alone; however those that deal with group instantiation come in 3 versions, with 2 flavors each (one Groovy, the other Java friendly).Locating a group configuration is easily done by specifying the name you're interested in findingdef configuration = app.mvcGroupManager.findConfiguration('foo')
create
methoddef configuration = app.mvcGroupManager.findConfiguration('foo') def group1 = configuration.create('foo1') def group2 = configuration.create('foo2', [someKey: 'someValue']) // the following will make the group's id match its name def group3 = configuration.create() def group4 = configuration.create(someKey: 'someValue')
Config.groovy
griffon.mvcid.collision = 'warning' // accepted values are 'warning', 'exception' (default)
def g1 = app.mvcGroupManager.groups.foo1 def g2 = app.mvcGroupManager.findGroup('foo1') def g3 = app.mvcGroupManager.foo1 assert g1 == g2 assert g1 == g3
app.mvcGroupManager.models.each { model ->
// do something with model
}
5.2.2 MVCGroups and Configuration
Now that you know there are several ways to instantiate MVC groups we can go back to customizing them.The simples way is to pass in new values as part of the arguments map that mvcGroupInit receives, for exampledef group = app.mvcGroupManager.buildMVCGroup('foo', [key: 'foo'])
config
key that every MVC group configuration may have then you must instantiate the group in the following waydef configuration = app.mvcGroupManager.cloneMVCConfiguration('foo', [key: 'someValue']) def group = configuration.create()
create()
method.
5.2.3 Configuration Options
The following options are available to all MVC groups as long as you use theconfig
key.Disabling Lifecycle Events
Every MVC group triggers a few events during the span of its lifetime. These events will be sent to the event bus even if no component is interested in handling them. There may be times when you don't want these events to be placed in the event bus in order to speed up group creation/destruction. Use the following configuration to gain this effect:mvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' config { events { lifecycle = false } } } }
Disabling Instantiation Events
The Griffon runtime will trigger an event for every artifact it manages. As with the previous events this one will be sent to the event bus even if no component handles it. Skipping publication of this event may result in a slight increase of speed during group instantiation. Use the following configuration to gain this effect:mvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' config { events { instantiation = false } } } }
Disabling Destruction Events
This is the counterpart of theNewInstance
event. Skipping publication of this event may result in a slight increase of speed when a group or any artifact instance is destroyed. Use the following configuration to gain this effect:mvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' config { events { destruction = false } } } }
Disabling Controllers as Application Event Listeners
Controllers are registered as application event handlers by default when a group in instantiated. This makes it very convenient to have them react to events placed in the application's event bus. However you may want to avoid this automatic registration altogether, as it can lead to performance improvements. You can disable this feature with the following configuration:mvcGroups { // MVC Group for "sample" 'sample' { model = 'sample.SampleModel' view = 'sample.SampleView' controller = 'sample.SampleController' config { events { listener = false } } } }
5.3 Runtime Configuration
The application's runtime configuration is available through theconfig
property of the application instance. This is a ConfigObject
whose contents are obtained by merging Application.groovy
and Config.groovy
. Builder configuration is available through the builderConfig
property and reflects the contents of Builder.groovy
. Configuration files may also be provided as properties files; settings on the matching script will be overridden by those set in the properties file.
5.3.1 Internationalization Support
Configuration files are i18n aware which means you can append locale specific strings to a configuration file, for exampleConfig_de_CH.groovy
. Locale suffixes are resolved from least to most specific; for a locale with language = 'de', country = 'CH' and variant = 'Basel' the following files are loaded in order
Config.groovy
Config.properties
Config_de.groovy
Config_de.properties
Config_de_CH.groovy
Config_de_CH.properties
Config_de_CH_Basel.groovy
Config_de_CH_Basel.properties
5.3.2 External Configuration Support
Some deployments require that configuration be sourced from more than one place and be changeable without requiring a rebuild of the application. In order to support deployment scenarios such as these the configuration can be externalized. To do so, point Griffon at the locations of the configuration files that should be used by adding agriffon.config.locations
setting in Config.groovy
, for example:griffon.config.locations = [ "classpath:${appName}-config.properties", "classpath:${appName}-config.groovy", "file:${userHome}/.griffon/${appName}-config.properties", "file:${userHome}/.griffon/${appName}-config.groovy"]
USER_HOME
.It is also possible to load config by specifying a class that is a config script.griffon.config.locations = [com.my.app.MyConfig]
config
property of the GriffonApplication object and are hence obtainable from there.Values that have the same name as previously defined values will overwrite the existing values, and the pointed to configuration sources are loaded in the order in which they are defined.
5.4 Application Lifecycle
Every Griffon application goes through the same life cycle phases no matter in which mode they are running, with the exception of applet mode where there is an additional phase due to the intrinsic nature of applets. The application's lifecycle has been inspired by JSR-296, the Swing Application Framework.Every phase has an associated life cycle script that will be invoked at the appropriate time. These scripts are guaranteed to be invoked inside the UI thread (the Event Dispatch Thread in Swing). The script names match each phase name; you'll find them insidegriffon-app/lifecycle
.
5.4.1 Initialize
The initialization phase is the first to be called by the application's life cycle. The application instance has just been created and its configuration has been read. No other artifact has been created at this point, which means that event publishing and theArtifactManager
are not yet available to the script's binding.This phase is typically used to tweak the application for the current platform, including its Look & Feel.Addons will be initialized during this phase.
The Initialize
script will be called right after the configuration has been read but before addons are initialized. You wont have access to addon contributions.
5.4.2 Startup
This phase is responsible for instantiating all MVC groups that have been defined in the application's configuration (Application.groovy
) and that also have been marked as startup groups in the same configuration file.
The Startup
script will be called after all MVC groups have been initialized.
5.4.3 Ready
This phase will be called right after Startup with the condition that no pending events are available in the UI queue. The application's main frame will be displayed at the end of this phase.5.4.4 Shutdown
Called when the application is about to close. Any artifact can invoke the shutdown sequence by callingshutdown()
on the app
instance.TheShutdown
script will be called before anyShutdownHandler
or event handler interested in theShutdownStart
event.
5.4.5 Stop
This phase is only available when running on applet mode. It will be called when the applet container invokesdestroy()
on the applet instance.
5.5 Application Events
Applications have the ability to publish events from time to time to communicate that something of interest has happened at runtime. Events will be triggered by the application during each of its life cycle phases, also when MVC groups are created and destroyed.All application event handlers are guaranteed to be called in the same thread that originated the event.Any artifact or class can trigger an application event, by routing it through the reference to the current running application instance. All artifacts posses an instance variable that points to that reference. All other classes can use ApplicationHolder to gain access to the current application's instance.Publishing an event can be done synchronously on the current thread or asynchronously relative to the UI thread. For example, the following snippet will trigger an event that will be handled in the same thread, which could be the UI thread itself
app.event('MyEventName', ['arg0', 'arg1'])
MyEventName
will be called outside of the UI threadapp.eventOutsideUI('MyEventName', ['arg0', 'arg1'])
app.eventAsync('MyEventName', ['arg0', 'arg1'])
app.eventPublishingEnabled = false
app.eventPublishingEnabled = true
5.5.1 Life Cycle Events
The following events will be triggered by the application during each one of its phasesBootstrapStart[app]
- after logging configuration has been setup, during the Initialize phase.BootstrapEnd[app]
- at the end of the Initialize phase.StartupStart[app]
- at the beginning of the Startup phase.StartupEnd[app]
- at the end of the Startup phase.ReadyStart[app]
- at the beginning of the Startup phase.ReadyEnd[app]
- at the end of the Startup phase.ShutdownRequested[app]
- before the Shutdown begins.ShutdownAborted[app]
- if a Shutdown Handler prevented the application from entering the Shutdown phase.ShutdownStart[app]
- at the beginning of the Shutdown phase.
5.5.2 Artifact Events
The following events will be triggered by the application when dealing with artifactsNewInstance[klass, type, instance]
- when a new artifact is created.DestroyInstance[klass, type, instance]
- when an artifact instance is destroyed.LoadAddonsStart[app]
- before any addons are initialized, during the Initialize phase.LoadAddonsEnd[app, addons]
- after all addons have been initialized, during the Initialize phase.addons
is a Map of <name, instance> pairs.LoadAddonStart[name, addon, app]
- before an addon is initialized, during the Initialize phase.LoadAddonEnd[name, addon, app]
- after an addon has been initialized, during the Initialize phase.
InitializeMVCGroup[configuration, group]
- when a new MVC group is initialized.configuration
is of type MVCGroupConfiguration;group
is of type MVCGroup.CreateMVCGroup[group]
- when a new MVC group is created.configuration
is of type MVCGroupConfiguration;group
is of type MVCGroup.DestroyMVCGroup[group]
- when an MVC group is destroyed.group
is of type MVCGroup.
5.5.3 Miscellaneous Events
These events will be triggered when a specific condition is reachedUncaughtExceptionThrown[exception]
- when an uncaught exception bubbles up to GriffonExceptionHandler.WindowShown[window]
- triggered by the WindowManager when a Window is shown.WindowHidden[window]
- triggered by the WindowManager when a Window is hidden.
5.5.4 Custom Events
Any artifact that holds a reference to the current application may trigger events at its leisure by calling theevent()
or eventAsync
methods on the application instance. The following example demonstrates how a Controller triggers a "Done" event after an action has finishedclass MyController { def action = { evt = null -> // do some work app.event('Done') } }
event()
method. The first takes just the name of the event to be published; the second accepts an additional argument which should be a List of parameters to be sent to every event handler. Event handlers notified by this method are guaranteed to process the event in the same thread that published it. However, if what you need is to post a new event and return immediately then use the eventAsync
variants. If you want the event to be handled outside of the UI thread then use the eventOutsideUI()
variants.
5.5.5 Event Handlers
Any artifact or class that abides to the following conventions can be registered as an application listener, those conventions are:- it is a Script, class, Map, RunnableWithArgs or closure.
- in the case of scripts or classes, they must define an event handler whose name matches on<EventName>, this handler can be a method, RunnableWithArgs or a closure property.
- in the case of a Map, each key maps to <EventName>, the value must be a RunnableWithArgs or a closure.
- scripts, classes and maps can be registered/unregistered by calling
addApplicationListener
/removeApplicationListener
on the app instance. - RunnableWithArgs and closure event handlers must be registered with an overloaded version of
addApplicationListener
/removeApplicationListener
that takes <EventName> as the first parameter, and the runnable/closure itself as the second parameter.
Events.groovy
inside griffon-app/conf
. Lastly both Controller and Service instances are automatically registered as application event listeners. This is the only way to declare event listeners for the BootstrapStart
event.You can also write a class namedThese are some examples of event handlers:Events.java
insrc/main
as an alternative togriffon-app/conf/Events.groovy
, but not both!
- Display a message right before default MVC groups are instantiated
File: griffon-app/conf/Events.groovy
onBootstrapEnd = { app -> println """Application configuration has finished loading. MVC Groups will be initialized now.""" }
- Quit the application when an uncaught exception is thrown
File: src/main/Events.java
import griffon.util.ApplicationHolder;public class Events { public void onUncaughtExceptionThrown(Exception e) { ApplicationHolder.getApplication().shutdown(); } }
- Print the name of the application plus a short message when the application is about to shut down.
File: griffon-app/controller/MyController.groovy
class MyController {
def onShutdownStart = { app ->
println "${app.config.application.title} is shutting down"
}
}
- Print a message every time the event "Foo" is published
File: griffon-app/controller/MyController.groovy
class MyController { void mvcGroupInit(Map args) { app.addApplicationListener([ Foo: {-> println 'got foo!' } ]) } def fooAction = { evt = null -> // do something app.event('Foo') } }
- An alternative to the previous example using a closure event handler
File: griffon-app/controller/MyController.groovy
class MyController { void mvcGroupInit(Map args) { app.addApplicationListener('Foo'){-> println 'got foo!' } } def fooAction = { evt = null -> // do something app.event('Foo') } }
- Second alternative to the previous example using a RunnableWithArgs event handler
File: griffon-app/controller/MyController.java
import java.util.Map; import griffon.util.RunnableWithArgs; import org.codehaus.griffon.runtime.core.AbstractGriffonController;public class MyController extends AbstractGriffonController { public void mvcGroupInit(Map<String, Object> params) { getApp().addApplicationListener("Foo", new RunnableWithArgs() { public void run(Object[] args) { System.out.println("got foo!"); } }); } public void fooAction() { // do something getApp().event("Foo"); } }
5.5.6 Custom Event Publishers
As the name implies application events are sent system wide. However there is an option to create localized event publishers. Griffon provides a @griffon.transform.EventPublisher AST transformation that you can apply to any class that wishes to be an event publisher.This AST transformation will inject the following methods to the annotated classes:- addEventListener(Object)
- addEventListener(String, Closure)
- addEventListener(String, RunnableWithArgs)
- removeEventListener(Object)
- removeEventListener(String, Closure)
- removeEventListener(String, RunnableWithArgs)
- publishEvent(String)
- publishEvent(String,List)
- publishEventOutsideUI(String)
- publishEventOutsideUI(String,List)
- publishEventAsync(String)
- publishEventAsync(String,List)
- isEventPublishingEnabled()
- setEventPublishingEnabled(boolean)
@griffon.transform.EventPublisher class Publisher { void doit(String name) { publishEvent('arg', [name]) } void doit() { publishEvent('empty') } }class Consumer { String value void onArg(String arg) { value = 'arg = ' + arg } void onEmpty() { value = 'empty' } }p = new Publisher() c = new Consumer() p.addEventListener(c) assert !c.value p.doit() assert c.value == 'empty' p.doit('Groovy') assert c.value == 'arg = Groovy'
5.6 Application Features
The GriffonApplication interface defines the base contract for all Griffon applications. However there are some meta enhancements done at runtime to all applications. The following methods become available before the Initialize phase is executed:- MVC
- Threading
5.6.1 Metadata
Access to the application's metadata file (application.properties
) is available by querying the griffon.util.Metadata
singleton. Here's a snippet of code that shows how to setup a welcome message that displays the application's name and version, along with its Griffon versionimport griffon.util.Metadatadef meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 2 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" }
getApplicationName()
- same result asmeta['app.name']
getApplicationVersion()
- same result asmeta['app.version']
getApplicationToolkit()
- same result asmeta['app.toolkit']
getGriffonVersion()
- same result asmeta['app.griffon.version']
getGriffonStartDir()
- returns the value of'griffon.start.dir'
from the System propertiesgetGriffonWorkingDir()
- returns a File that points to'griffon.start.dir'
if the value is set and the file is writable, otherwise returns a File pointing to the current location if it is writable; if that fails then attempts to return a File pointing to'user.dir'
; if all fail it will return the location to a temporal file, typically'/tmp/${griffonAppName}'
.
5.6.2 Environment
A Griffon application can run in several environments, default ones being DEVELOPMENT, TEST and PRODUCTION. An application can inspect its current running environment by means of thegriifon.util.Environment
enum.The following example enhances the previous one by displaying the current running environmentimport griffon.util.Metadata import griffon.util.Environmentdef meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 3 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" label "Current environment is ${Environment.current}" }
5.6.3 Running Mode
Applications can run in any of the following modes: STANDALONE, WEBSTART or APPLET. Thegriffon.util.RunMode
enum allows access to the current running mode.This example extends the previous one by adding information on the current running modeimport griffon.util.Metadata import griffon.util.Environment import griffon.util.RunModedef meta = Metadata.current application(title: "Some app", package: true) { gridLayout cols: 1, rows: 3 label "Hello, I'm ${meta['app.name']}-${meta['app.version']}" label "Built with Griffon ${meta['app.griffon.version']}" label "Current environment is ${Environment.current}" label "Current running mode is ${RunMode.current}" }
5.6.4 Shutdown Handlers
Applications have the option to let particular artifacts abort the shutdown sequence and/or perform a task while the shutdown sequence is in process. Artifacts that desire to be part of the shutdown sequence should implement thegriffon.core.ShutdownHandler
interface and register themselves with the application instance.The contract of a ShutdownHandler
is very simple
boolean canShutdown(GriffonApplication app)
- returnfalse
to abort the shutdown sequence.void onShutdown(GriffonApplication app)
- called if the shutdown sequence was not aborted.
5.6.5 Application Phase
All applications have the same life-cycle phases. You can inspect in which phase the application is currently on by calling thegetPhase()
method on an application instance. Valid values are defined by the ApplicationPhase enum : INITIALIZE
, STARTUP
, READY
, MAIN
and SHUTDOWN
.
5.6.6 Application Locale
Starting with Griffon 0.9 applications have a boundlocale
property that is initialized to the default Locale. Components can listen to Locale changes by registering themselves as PropertyChangeListeners on the application instance.The value of this property can be changed at any time. Doing so will also update the value of the default Locale used in the currently running JVM process.You may specify a configuration flag in Application.groovy
that can be used as the initial value for this property, like thisapplication {
title = 'Sample'
startupGroups = ['foo'] locale = 'es // Should Griffon exit when no Griffon created frames are showing?
autoShutdown = true // If you want some non-standard application class, apply it here
//frameClass = 'javax.swing.JFrame'
}
java.util.Locale
or java.util.String
with the following formats:
language
language_country
language_country_variant
5.6.7 Default Imports
Since Griffon 0.9.1 default imports per artifacts are supported. All Groovy based artifacts will resolve classes from thegriffon.core
and griffon.util
packages automatically, there is no longer a need to define imports on those classes unless you require an static import or define an alias. An example of this feature would be as follows.class MyController { void mvcGroupInit(Map args) { println Metadata.current.'app.name' } }
Metadata
class is defined in package griffon.util
.
There are additional imports per artifact type, here's the list of default definitions
- Model
- groovy.beans -> @Bindable, @Vetoable
- java.beans -> useful for all PropertyChange* classes
- View (when using Swing)
- java.awt
- java.awt.event
- javax.swing
- javax.swing.event
- javax.swing.table
- javax.swing.text
- javax.swing.tree
META-INF/griffon-default-imports.properties
with the following format<artifact_type> = <comma_separated_package_list>
views = javax.swing., javax.swing.event., javax.swing.table., javax.swing.text., javax.swing.tree., java.awt., java.awt.event. models = groovy.beans., java.beans.
5.6.8 Startup Arguments
Command line arguments can be passed to the application and be accessed by callinggetStartupArgs()
on the application instance. This will return a copy of the args (if any) defined at the command line.Here's a typical example of this feature in development modegriffon run-app arg0 arg1 argn
griffon dev package jar
java -jar dist/jars/app.jar arg0 arg1 argn
5.6.9 Uncaught Exceptions
There are times when an exception catches you off guard. The JVM provides a mechanism for handling these kind of exceptions: Thread.UncaughtExceptionHandler. You can register an instance that implements this interface with a Thread or ThreadGroup, however it's very likely that exceptions thrown inside the EDT will not be caught by such instance. Furthermore, it might be the case that other components would like to be notified when an uncaught exception is thrown. This is precisely what GriffonExceptionHandler does.Stack traces will be sanitized by default, in other words, you won't see a long list containing Groovy internals. However you can bypass the filtering process and instruct Griffon to leave the stack traces untouched by specifying the following flag either in the command line with-D
switch or in Config.groovy
griffon.full.stacktrace = true
error
level. Should you choose to disable logging but still have some output when an exception occurs then configure the following flaggriffon.exception.output = true
- Uncaught<exception.class.simpleName>
- UncaughtExceptionThrown
IllegalArgumentException
during the invocation of a service method. This method was called from within a Controller which defines a handler for this exception, like thisclass SampleService { void work() { throw new IllegalArgumentException('boom!') } }class SampleController { def sampleService def someAction = { sampleService.work() } def onUncaughtIllegalArgumentException = { iae -> // process exception } }
IllegalArgumentException
if you declare a handler for RuntimeException
. You can however, rely on the second event triggered by this mechanism. Be aware that every single exception will trigger 2 events each time it is caught. It is best to use a synchronization approach to keep track of the last exception caught, like soimport groovy.transform.Synchronizedclass SampleController { private lastCaughtException @Synchronized void onUncaughtRuntimeException(RuntimeException e) { lastCaughtException = e // handle runtime exception only } @Synchronized void onUncaughtExceptionThrown(e) { if(lastCaughtException == e) return lastCaughtException = e // handle any other exception types } }
GriffonExceptionHandler
again, they will simply be logged and discarded instead.
5.7 WindowManager
Although the following API directly refers to Swing in all examples it's possible to useWindowManager
with other toolkits such as javafx and SWT as these plugins provide their own WindowManager
implementation plus helper classes.The WindowManager
class is responsible for keeping track of all the windows managed by the application. It also controls how these windows are displayed (via a pair of methods: show, hide). WindowManager relies on an instance of WindowDisplayHandler
to actually show or hide a window. The default implementation simple shows and hide windows directly, however you can change this behavior by setting a different implementation of WindowDisplayHandler
on the application instance.WindowManager DSL
Starting with Griffon 0.9.2 there's a new DSL for configuring show/hide behavior per window. This configuration can be set ingriffon-app/conf/Config.groovy
, and here is how it looksswing { windowManager { myWindowName = [ show: {window, app -> … }, hide: {window, app -> … } ] myOtherWindowName = [ show: {window, app -> … } ] } }
- show - used to show the window to the screen. It must be a closure that takes two parameters: the window to display and the current application.
- hide - used to hide the window from the screen. It must be a closure that takes two parameters: the window to hide and the current application.
- handler - a custom
WindowDisplayHandler
.
swing {
windowManager {
defaultHandler = new MyCustomWindowDisplayHandler()
}
}
swing { windowManager { defaultShow = {window, app -> … } // defaultHide = {window, app -> … } someWindowName = [ hide: {window, app -> … } ] } }
Custom WindowDisplayHandlers
The following example shows how you can animate all managed windows using a dropIn effect for show() and a dropOut effect for hide(). This code assumes you have installed the Effects plugin.Insrc/main/Dropper.groovy
import java.awt.Window import griffon.swing.SwingUtils import griffon.swing.DefaultWindowDisplayHandler import griffon.core.GriffonApplication import griffon.effects.Effectsclass Dropper extends DefaultWindowDisplayHandler { void show(Window window, GriffonApplication app) { SwingUtils.centerOnScreen(window) app.execOutsideUI { Effects.dropIn(window, wait: true) } } void hide(Window window, GriffonApplication app) { app.execOutsideUI { Effects.dropOut(window, wait: true) } } }
griffon-app/conf/Events.groovy
// No windows have been created before this step onBootstrapEnd = { app -> app.windowDisplayHandler = new Dropper() }
Custom WindowDisplayHandler
implementations set in this manner will be called for all managed windows. You'll loose the ability of using the WindowManager DSL.
Alternatively, you could specify an instance of Dropper
as the default handler by changing the WindowManager
's configuration toswing {
windowManager {
defaultHandler = new Dropper()
}
}
WindowDisplayHandler
interface also defines show/hide methods that can manage JInternalFrame
instances.Starting Window
Previous to Griffon 0.9.2 the first window to be displayed during the Ready phase was determined by a simple algorithm: picking the first available window from the managed windows list. With 0.9.2 however, it's now possible to configure this behavior by means of the WindowManager DSL. Simply specify a value forswing.windowManager.startingWindow
, like thisswing { windowManager { startingWindow = 'primary' } }
- a String that defines the name of the Window. You must make sure the Window has a matching name property.
- a Number that defines the index of the Window in the list of managed windows.
5.8 Artifact API
The Artifact API provides introspection capabilities on the conventions set on each artifact type. The following sections explain further what you can do with this API.5.8.1 Evaluating Conventions
Every Griffon application exposes all information about its artifacts and addons via a pair of helper classesAddonManager
- used for all installed addonsArtifactManager
- used for all remaining artifacts
ArtifactManager
TheArtifactManager
class provides methods to evaluate the conventions within the project and internally stores references to all classes within a GriffonApplication using subclasses of GriffonClass class.A GriffonClass
represents a physical Griffon resources such as a controller or a service. For example to get all GriffonClass
instances you can call:app.artifactManager.allClasses.each { println it.name }
ArtifactManager
instance possesses that allow you to narrow the type of artifact you are interested in. For example if you only need to deal with controllers you can do:app.artifactManager.controllerClasses.each { println it.name }
get*Classes
- Retrieves all the classes for a particular artifact type. Exampleapp.artifactManager.getControllerClasses()
.*Classes
- Retrieves all the classes for a particular artifact type. Exampleapp.artifactManager.controllerClasses
.is*Class
- Returns true if the given class is of the given artifact type. Exampleapp.artifactManager.isControllerClass(ExampleController)
GriffonClass
interface itself provides a number of useful methods that allow you to further evaluate and work with the conventions. These include:
newInstance
- Creates a new instance of the enclosed class.getName
- Returns the logical name of the class in the application without the trailing convention part if applicablegetClazz
- Returns the artifact classgetType
- Returns the type of the artifact, i.e "controller"getTrailing
- Returns the suffix (if any) of the artifact, i.e "Controller"
AddonManager
TheAddonManager
class is responsible for holding references to all addons (which are of type griffon.core.GriffonAddon), as well as providing metainformation on each addon via an addon descriptor. The latter can be used to know at runtime the name and version of a particular addon, useful for building a dynamic About dialog for example.All addons have the same behavior which is explained in detail in the Addons section.
5.8.2 Adding Dynamic Methods at Runtime
For Griffon managed classes like controllers, models and so forth you can add methods, constructors etc. using the ExpandoMetaClass mechanism by accessing each controller's MetaClass:class ExampleAddon {
def addonPostInit(app) {
app.artifactManager.controllerClasses.each { controllerClass ->
controllerClass.metaClass.myNewMethod = {-> println "hello world" }
}
}
}
app.artifactManager
object to get a reference to all of the controller classes' MetaClass instances and then add a new method called myNewMethod
to each controller.
Alternatively, if you know before hand the class you wish add a method to you can simple reference that classes metaClass
property:class ExampleAddon { def addonPostInit(app) { String.metaClass.swapCase = {-> def sb = new StringBuffer() delegate.each { sb << (Character.isUpperCase(it as char) ? Character.toLowerCase(it as char) : Character.toUpperCase(it as char)) } sb.toString() } assert "UpAndDown" == "uPaNDdOWN".swapCase() } }
swapCase
to java.lang.String
directly by accessing its metaClass
.
5.8.3 Artifact Types
All Griffon artifacts share common behavior. This behavior is captured by an interface named griffon.core.GriffonArtifact. Additional interfaces with more explicit behavior exist per each artifact type. The following is a list of the basic types and their corresponding interface- Model -> griffon.core.GriffonModel
- View -> griffon.core.GriffonView
- Controller -> griffon.core.GriffonController
- Service -> griffon.core.GriffonService
AST injection is always enabled unless you disable it as explained in the Disable AST Injection section.Additionally to each artifact type you will find a companion GriffonClass that is specialized for each type. These specialized classes can be used to discover metadata about a particular artifact. The following is a list of the companion GriffonClass for each of the basic artifacts found in core
- Model -> griffon.core.GriffonModelClass
- View -> griffon.core.GriffonViewClass
- Controller -> griffon.core.GriffonControllerClass
- Service -> griffon.core.GriffonServiceClass
ArtifactManager
.
5.9 Archetypes
While it's true that artifact templates can be provided by plugins it simply was not possible to configure how an application is created. Application Archetypes fill this gap by providing a hook into the application creation process. Archetypes can do the following:- provide new versions of existing templates, like Model, Controller and so forth
- create new directories and files
- most importantly perhaps, install a preset of plugins
$USER_HOME/.griffon/<version>/archetypes
. Archetypes are registered with an application's metadata when creating an application. You can either manually modify the value of 'app.archetype' to a known archetype name or specify an -archetype=<archetypeName>
flag when creating a new application.If no valid archetype is found then the default archetype will be used. Following is the default template for an application archetypeimport griffon.util.MetadataincludeTargets << griffonScript('CreateMvc')target(name: 'createApplicationProject', description: 'Creates a new application project', prehook: null, posthook: null) { createProjectWithDefaults() createMVC() // to install plugins do the following // Metadata md = Metadata.getInstance(new File("${basedir}/application.properties")) // // for a single plugin // installPluginExternal md, pluginName, pluginVersion // ** pluginVersion is optional ** // // for multiple plugins where the latest version is preferred // installPluginsLatest md, [pluginName1, pluginName2] // // for multiple plugins with an specific version // installPlugins md, [pluginName1: pluginVersion1] } setDefaultTarget(createApplicationProject)
5.9.1 A Fancy Example
This section demonstrates how an archetype can be created and put to good use for building applications.#1 Create the archetype
The first step is to create the archetype project and its descriptor, which can be done by executing the following commandgriffon create-archetype fancy cd fancy
#2 Tweak the archetype descriptor
Locate the archetype descriptor (application.groovy
) and open it in your favorite editor, paste the following snippetimport griffon.util.MetadataincludeTargets << griffonScript('CreateMvc')target(name: 'createApplicationProject', description: 'Creates a new application project', prehook: null, posthook: null) { createProjectWithDefaults() argsMap.model = 'MainModel' argsMap.view = 'MainView' argsMap.controller = 'MainController' createMVC() createArtifact( name: mvcFullQualifiedClassName, suffix: 'Actions', type: 'MainActions', path: 'griffon-app/views') createArtifact( name: mvcFullQualifiedClassName, suffix: 'MenuBar', type: 'MainMenuBar', path: 'griffon-app/views') createArtifact( name: mvcFullQualifiedClassName, suffix: 'StatusBar', type: 'MainStatusBar', path: 'griffon-app/views') createArtifact( name: mvcFullQualifiedClassName, suffix: 'Content', type: 'MainContent', path: 'griffon-app/views') Metadata md = Metadata.getInstance(new File("${basedir}/application.properties")) installPluginExternal md, 'miglayout' }setDefaultTarget(createApplicationProject)
griffon-app/views
. Finally it installs the latest version of the MigLayout plugin.#3 Create the artifact templates
According to the conventions laid out in the archetype descriptor there must exist a file undertemplates/artifacts
that matches each one of the specified artifact types, in other words we need the following filesMainModel.groovy
@artifact.package@import groovy.beans.Bindable import griffon.util.GriffonNameUtilsclass @artifact.name@ { @Bindable String status void mvcGroupInit(Map args) { status = "Welcome to ${GriffonNameUtils.capitalize(app.config.application.title)}" } }
@artifact.package@class @artifact.name@ { 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 // } def newAction = { evt = null -> model.status = 'New action' } def openAction = { evt = null -> model.status = 'Open action' } def saveAction = { evt = null -> model.status = 'Save action' } def saveAsAction = { evt = null -> model.status = 'Save As action' } def aboutAction = { evt = null -> model.status = 'About action' } def quitAction = { evt = null -> model.status = 'Quit action' } def cutAction = { evt = null -> model.status = 'Cut action' } def copyAction = { evt = null -> model.status = 'Copy action' } def pasteAction = { evt = null -> model.status = 'Paste action' } }
@artifact.package@build(@artifact.name.plain@Actions) application(title: GriffonNameUtils.capitalize(app.config.application.title), pack: true, locationByPlatform:true, iconImage: imageIcon('/griffon-icon-48x48.png').image, iconImages: [imageIcon('/griffon-icon-48x48.png').image, imageIcon('/griffon-icon-32x32.png').image, imageIcon('/griffon-icon-16x16.png').image]) { menuBar(build(@artifact.name.plain@MenuBar)) migLayout(layoutConstraints: 'fill') widget(build(@artifact.name.plain@Content), constraints: 'center, grow') widget(build(@artifact.name.plain@StatusBar), constraints: 'south, grow') }
@artifact.package@import groovy.ui.Consoleactions { action( id: 'newAction', name: 'New', closure: controller.newAction, mnemonic: 'N', accelerator: shortcut('N'), smallIcon: imageIcon(resource:"icons/page.png", class: Console), shortDescription: 'New' ) action( id: 'openAction', name: 'Open...', closure: controller.openAction, mnemonic: 'O', accelerator: shortcut('O'), smallIcon: imageIcon(resource:"icons/folder_page.png", class: Console), shortDescription: 'Open' ) action( id: 'quitAction', name: 'Quit', closure: controller.quitAction, mnemonic: 'Q', accelerator: shortcut('Q'), ) action( id: 'aboutAction', name: 'About', closure: controller.aboutAction, mnemonic: 'B', accelerator: shortcut('B') ) action( id: 'saveAction', name: 'Save', closure: controller.saveAction, mnemonic: 'S', accelerator: shortcut('S'), smallIcon: imageIcon(resource:"icons/disk.png", class: Console), shortDescription: 'Save' ) action( id: 'saveAsAction', name: 'Save as...', closure: controller.saveAsAction, accelerator: shortcut('shift S') ) action(id: 'cutAction', name: 'Cut', closure: controller.cutAction, mnemonic: 'T', accelerator: shortcut('X'), smallIcon: imageIcon(resource:"icons/cut.png", class: Console), shortDescription: 'Cut' ) action(id: 'copyAction', name: 'Copy', closure: controller.copyAction, mnemonic: 'C', accelerator: shortcut('C'), smallIcon: imageIcon(resource:"icons/page_copy.png", class: Console), shortDescription: 'Copy' ) action(id: 'pasteAction', name: 'Paste', closure: controller.pasteAction, mnemonic: 'P', accelerator: shortcut('V'), smallIcon: imageIcon(resource:"icons/page_paste.png", class: Console), shortDescription: 'Paste' ) }
@artifact.package@import static griffon.util.GriffonApplicationUtils.*menuBar = menuBar { menu(text: 'File', mnemonic: 'F') { menuItem(newAction) menuItem(openAction) separator() menuItem(saveAction) menuItem(saveAsAction) if( !isMacOSX ) { separator() menuItem(quitAction) } } menu(text: 'Edit', mnemonic: 'E') { menuItem(cutAction) menuItem(copyAction) menuItem(pasteAction) } if(!isMacOSX) { glue() menu(text: 'Help', mnemonic: 'H') { menuItem(aboutAction) } } }
@artifact.package@label('Main content')
@artifact.package@label(id: 'status', text: bind { model.status })
#4 Package and install the archetype
This step is easily done with a pair of command invocationsgriffon package-archetype griffon install-archetype target/package/griffon-fancy-0.1.zip
#5 Use the archetype
Now that the archetype has been installed all that is left is put it to good use, like thisgriffon create-app sample --archetype=fancy
- griffon-app
- controllers
- sample
SampleController
- models
- sample
sampleModel
- views
- sample
SampleActions
SampleContent
SampleMenuBar
SampleStatusBar
SampleView
application.properties
file you'll notice that the miglayout plugin has been installed too.Archetypes can be versioned, installed and released in the same way plugins are.
5.10 Platform Specific
The following sections outline specific tweaks and options available for a particular platform.5.10.1 Tweaks for a Particular Platform
Griffon will automatically apply tweaks to the application depending on the current platform. However you have the option to specify a different set of tweaks. For example, the following configuration inConfig.groovy
specifies a different handler for macosx
:platform { handler = [ macosx: 'com.acme.MyMacOSXPlatformHandler' ] }
package com.acmeimport griffon.core.GriffonApplication import griffon.util.PlatformHandlerclass MyMacOSXPlatformHandler implements PlatformHandler { void handle(GriffonApplication app) { System.setProperty('apple.laf.useScreenMenuBar', 'true') … } }
linux
, macosx
, solaris
and windows
.
5.10.2 MacOSX
Applications that run in Apple's MacOSX must adhere to an strict set of rules. We recommend you to have a look at Apple's (Human Interface Guidelines).Griffon makes it easier to integrate with MacOSX by automatically registering a couple of System properties that make the applicaiton behave like a native oneapple.laf.useScreenMenuBar
- if set to true will force the application's menu bar to appear at the top. Griffon sets its value to true.com.apple.mrj.application.apple.menu.about.name
- sets the name that will appear next to theAbout
menu option.
About
, Preferences
and Quit
menu options. The default handlers will trigger an specific application event each. These events can be disabled with a command flag set in griffon-app/conf/Config.groovy
. The following table outlines the events, flags and the default behavior when the flags are enabledEvent | Fired when | Flag | Default behavior |
---|---|---|---|
OSXAbout | user activates About menu | osx.noabout | Default about dialog is displayed |
OSXPrefs | user activates Preferences menu | osx.noprefs | No Preferences menu is available |
OSXQuit | user activates Quit menu | osx.noquit | Application shutdowns immediately |
5.11 Manager Configuration
There are several components in a Griffon application that perform specific chores and tasks, they are usually know as managers. Some of them are theMVCGroupManager
, ArtifactManager
and AddonManager
for example. All of these helper components are instantiated using default implementations chosen by the Griffon runtime, however developers may specify custom implementations, and in some cases, disable them altogether.The following paragraphs enumerate the different managers and helpers that may be configured at booting time. Configuration is performed by adding the appropriate flag and value to Config.groovy
.LogManager
Discussed in: Logging.Responsibility: configure logging subsystem.Configuration flag:app.logManager.factory
Type: griffon.core.factories.LogManagerFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultLogManagerFactory
EventRouter
Discussed in: Application Events.Responsibility: publish events.Configuration flag:app.eventRouter.factory
Type: griffon.core.factories.EventRouterFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultEventRouterFactory
ArtifactManager
Discussed in: Artifact API.Responsibility: keep track ofGriffonArtifactClass
and GriffonArtifact
instances.Configuration flag: app.artifactManager.factory
Type: griffon.core.factories.ArtifactManagerFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultArtifactManagerFactory
AddonManager
Discussed in: Artifact API.Responsibility: keep track of registeredGriffonAddon
instances.Configuration flag: app.addonManager.factory
Type: griffon.core.factories.AddonManagerFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultAddonManagerFactory
MVCGroupManager
Discussed in: MVCGroupManager.Responsibility: create and destroyMVCGroup
instances.Configuration flag: app.mvcGroupManager.factory
Type: griffon.core.factories.MVCGroupManagerFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultMVCGroupManagerFactory
MessageSource
Discussed in: The Messsage Source.Responsibility: resolving internationalizable messages.Configuration flag:app.messageSource.factory
Type: griffon.core.factories.MessageSourceFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultMessageSourceFactory
ResourceResolver
Discussed in: Resolving Configured Resources.Responsibility: resolving resources.Configuration flag:app.resourceResolver.factory
Type: griffon.core.factories.ResourceResolverFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultResourceResolverFactory
ResourcesInjector
Discussed in: Automatically Injected Resources.Responsibility: injecting configured resources into freshly instantiated objects.Configuration flag:app.resourceInjector.factory
Type: griffon.core.factories.ResourcesInjectorFactory
Default implementation: org.codehaus.griffon.runtime.core.factories.DefaultResourcesInjectorFactory
GriffonControllerActionManager
Discussed in: The Action Manager.Responsibility: instantiate View friendly controller actions.Configuration flag:app.actionManager.factory
Disabling flag: griffon.action.manager.disable
Type: griffon.core.factories.GriffonControllerActionManagerFactory
Default implementation: UI toolkit specific