Monday, April 19, 2010

Quote of the Day

Most teams purporting to be doing agile software development are not applying the level of technical rigor necessary to succeed at it. Most "agile" team have actually only adopted Scrum's project-management practice and have failed to effectively adopt "the hard disciplines" like test-driven development, refactoring, pair programming, simple design, and continuous integration.


- Formal Versus Agile: Survival of the Fittest - IEEE Computer 2009 September


Friday, April 16, 2010

Use controller action via listeners in Griffon view (Updated)

I have been asked recently a couple of times about how to use listeners in Griffon view for event handling such as mousePressed or focusLost. Apparently neither Swing builder nor Griffon documented this aspect in details, and it has been a bit confusing for some folks to figure it out since the usage is slightly different from regular actions.

In Griffon view you can supply a predefined action to a component such as JButton to invoke a controller action in a breeze. The following code snippet demonstrate how it is done in Griffon:


actions {
action(id: "quitAction",
name: messageSource.getMessage('menuItem.exit.caption'),
mnemonic: "x",
closure: controller.quit)
}

...

menuBar {
menu(messageSource.getMessage('menu.file.caption')) {
menuItem(quitAction)
}
}

...


From Andres Almiray:

The main difference between all examples is that the first assigns an instance of javax.swing.Action to the node's value. The builder knows that it can configure a button/menuItem/etc out of an Action so it does it.



When it comes to listener and event handling, one would expect a similar pattern applies; for example according to Swing builder documentation mousePressed event exists on JComponent level so one would hope the following code can bind a controller action to an mousePressed event.


actions {
action(id: "nodeSelectionAction",
closure: controller.selectNode)
}

list(id:'nodeList', model: model.serverListModel, eventPressed: nodeSelectionAction)


From Andres Almiray:
The second example does not work because it attempts to assign an instance of javax.swing.Action as the value of the menuPressed property, which has to be a MouseListener. That's why it breaks (although the error text may be misleading).



Unfortunately you will get No such property: mousePressed for class: javax.swing.JList error message. Instead the correct way to do this is by using the closure instead of an Action since mousePressed expects a listener. Groovy closure declared here will be converted to a listener automatically:


list(id:'nodeList', model: model.serverListModel, mousePressed: controller.selectNode)

From Andres Almiray:
The third example works because it assigns a closure as the value of the mousePressed property. Groovy will generate under the covers a proxy of a MouseListener that uses that closure as the implementation of said proxy's mousePressed method.


Hope this post will save some investigation time for someone who is new to Griffon and Swing builder.

Go Griffon!

Tuesday, April 06, 2010

GValidation 0.2 release

After I released the very first version of the Griffon Validation plugin, I have received quite a few positive feedback, and thank everybody for all the feedback. Based on some initial bug report I have fixed some dependency problem with the initial version now the plugin is shipped with all dependencies it requires so you don't need to manually add jars into your project. Currently GValidation depends on:

  • Apache Commons Lang 2.5
  • Apache Commons Validator 1.3.1
  • Jakarta ORO 2.0.8
In the 0.2 release I have also fixed some inconsistency in some of the validator implementation, now all validator except nullable and blank will ignore both null and blank value. In other words, unless you provide nullable or blank validator the validation logic will assume all fields are optional by default.

To upgrade your application to the new plugin version please uninstall validation plugin first:

griffon uninstall-plugin validation
griffon install-plugin validation

I have also updated the Wiki with a quick tutorial showing how validation can be utilized in a simple but somewhat telling example. The same tutorial demo application binary can also be downloaded from the project files page.

Thursday, April 01, 2010

Grails like Validation plugin for Griffon

Recently I am working on a Griffon powered Swing application in one of my open source project Hydra Cache. While really love the convention over configuration setup and the grooviness of swing builder, soon I realized one tool I rely on frequently in Grails is missing in Griffon. Griffon does not tie the model to database via GORM as Grails does, which makes perfect sense since when building rich client application in many cases your model layer is not (or some will argue should not) directly tie to the database, after all that's why the 3-tier architecture was invented in the first place. That's when I start thinking if the convenient declaration based constraint and validation support in Grails can live without GORM but be applied on simple POGO model classes in Griffon. After writing validation listeners on the view a couple of times, my laziness pushed me to create a plugin to do just that. After a few positive and encouraging email exchange on dev@griffon.codehaus.org mail list and a few late night coding session, the first release of GValidation plugin is now ready for your evaluation. The plugin is implemented without dependency on Spring framework and purely implemented in Groovy.

To install the plugin use:

griffon install-plugin validation

Once the plugin is installed the following field and method will be automatically injected into your model classes:


Errors errors
boolean hasErrors()
boolean validation()


Here error field is a Groovy clone of Spring Validation Errors class in order to retain certain API consistency when compared with Grails validation support. Now in a typical scenario you can achieve the generic validation by first declare your constraints in the model:


class PersonModel {
@Bindable String name
@Bindable String email
@Bindable String blog

static constraints = {
name(blank: false)
email(blank: false, email: true)
blog(url: true)
}
}

Then perform the validation, usually in a controller action:



def doSomething = {evt = null ->
if (!model.validate()) {
doLater {
// display error messages
}
} else {
doLater {
// clear error messages
}

doOutside {
// do something interesting
}
}
}


You can also check and manipulate the errors generated by validation method using the errors object directly:



model.validate()
// do something else
..
if(model.hasErrors()){
model.errors.each{error->
// do something with the error
}
}

The only validators that are not ported from Grails are:
  • scale
  • unique
Since they are largely there to influence database schema generation rather than performing actual validation. A new validator inetAddress is introduced to ensure the field is a valid host name or IP address. For a complete list of the built-in validator shipped with this plugin please see the GValidation Wiki.

Just like Grails constraints, you can also implement your custom validator using Groovy closure:



// Simple custom validator
even( validator: {
return (it % 2) == 0
})


// Custom validator with access to the object under validation
password1( validator: {
val, obj ->
obj.properties['password2'] == val
})


// Custom validator with custom error
magicNumber( validator: {
val, obj ->
def result = checkMagicNumber()
if(!result)
obj.errors.rejectValue('magicNumber', 'customErrorCode')
return result
})




And last but not least once installed, GValidation allows you to enhance any POGO object in your application to have validation support, just simply use the ValidationEnhancer with your object:



import net.sourceforge.gvalidation.ValidationEnhancer

ValidationEnhancer.enhance(pogo)

pogo.validate()

poso.errors.each{
println it
}

For more information please check out the Wiki page, and feel free to send me your feedback or suggestion. For bug report please use the Bug Tracker.