Thursday, July 02, 2009

Griffon Action Patterns

I've been working on a decent-sized Griffon app recently and hit upon a pattern for managing actions that I thought might be useful to others. I began by following SwingPad's lead and declaring my actions separate (PSICATActions.groovy) from my view (PSICATView.groovy):

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.exit)
action(id: 'openAction', name: 'Open', closure: controller.open)
...
}


which I import into my view:

// build actions
build(PSICATActions)

application(title:'PSICAT', size:[800,600], locationByPlatform: true, layout: new MigLayout('fill')) {
...
}


This is nice because it keeps all of your action definitions in one place and doesn't clutter your view code with them. When implementing the actions, the common pattern seems to define closures on your controller:

class PSICATController {
def model
def view

void mvcGroupInit(Map args) {
...
}

def exit = { evt = null ->
...
}

def open = { evt = null ->
...
}
}


This is fine but when you start getting many actions your controller becomes cluttered and it is difficult to tell what is an action implementation and what is regular controller logic. You also have to be careful with naming:

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.exit)
action(id: 'openAction', name: 'Open', closure: controller.open)
action(id: 'newAction', name: 'New', closure: controller.new) // ERROR
}


We are forced to break with our naming convention because 'new' is a reserved keyword. To combat the clutter and avoid naming issues, I've taken to declaring my action implementations in a map on the controller:

class PSICATController {
def model
def view

void mvcGroupInit(Map args) {
...
}

def actions = [
'exit': { evt = null -> ... },
'open': { evt = null -> ... },
'new': { evt = null -> ... } // no issues here
]
}


Using the map makes it easy to tell what is an action and what is not, and linking the action definitions to the implementations becomes elegant and intuitive:

actions {
action(id: 'exitAction', name: 'Exit', closure: controller.actions['exit'])
action(id: 'openAction', name: 'Open', closure: controller.actions['open'])
action(id: 'newAction', name: 'New', closure: controller.actions['new'])
}


So far I have been pretty happy with this approach. What do other Griffon developers out there think?

2 comments:

Andres Almiray said...

Hi Josh, glad to see you're back into blogging, and about Griffon no less :-)

Personally I go with the following confventions:

1. all actions are of the form 'def actionName = { evt = null -> ...}'
2. controller logic is handled by methods like 'void doIt() { ... }'
3. methods are usually private
4. actions are declared at the top of the file, methods at the bottom

Cheers,
Andres

Josh Reed said...

Thanks for the comment Andres. One advantage that I see to using the actions map approach is you could do auto-binding between your action definition and the implementation. The old convention over configuration. So if when you declared your action, you don't specify a closure, it could attempt to bind to controller.actions[id - 'Action']. It would save one line per action definition, and you could override it by explicitly setting a closure.