Griffon Weather App
16 December, 2008
Written by James Williams
James Clarke recently updated his JSON Weather Service application to fête the release of JavaFX 1.0. I happened upon it on DZone and decided to port it to Griffon. The original source is 227 lines of code whereas the Griffon version is 151.
Here's the code breakdown:
| Name | Files | LOC |
| Controllers | 1 | 38 |
| Models | 1 | 13 |
| Views | 1 | 28 |
| Lifecycle | 5 | 68 |
| Integration Tests | 1 | 4 |
| Total | 9 | 151 |
The first major difference is that instead of an instance class Weather, all the variables live in the GriffonWeatherServiceModel.groovy.
import groovy.beans.Bindable
class GriffonWeatherServiceModel {
@Bindable stationName = ''
@Bindable clouds = ''
@Bindable windDirection = 0
@Bindable windSpeed = 0
@Bindable temperature = 0
@Bindable dewPoint = 0
@Bindable humidity = 0
@Bindable seaLevelPressure = 0
@Bindable observation = ''
@Bindable code = ''
}
One place where I was able to condense code was in GriffonWeatherServiceController.groovy.
import net.sf.json.JSONArray
import javax.swing.JOptionPane
import net.sf.json.groovy.JsonSlurper
class GriffonWeatherServiceController {
// these will be injected by Griffon
def model
def view
void mvcGroupInit(Map args) {
// this method is called after model and view are injected
}
def parseJSON = { evt = null, text ->
JsonSlurper slurper = new JsonSlurper()
def json = slurper.parseText(text)?.get('weatherObservation')
if (json != null) {
for (prop in model.getProperties()) {
if (json.get(prop.key) != null) {
model.{prop.key} = json.getString(prop.key)
}
}
} else {
JOptionPane.showMessageDialog(null, "Either the code is invalid or there was a problem.", "error", JOptionPane.ERROR_MESSAGE)
}
}
def getJSON = { url ->
doLater {
app.appFrames[0].getGlassPane().setSize(app.appFrames[0].getSize())
app.appFrames[0].getGlassPane().setVisible(true)
app.appFrames[0].getGlassPane().setBusy(true)
}
doOutside{
def result = new java.net.URL(url).getText()
parseJSON(null, result)
app.appFrames[0].pack()
app.appFrames[0].getGlassPane().setBusy(false)
app.appFrames[0].getGlassPane().setVisible(false)
}
}
}
Instead of walking the tree for each attribute, I inspect the properties of the model and see if they exist in the JSONObject. If so, they are assigned. If an incorrect code is entered (or there is no Internet), an error dialog appears. Retrieving and parsing the JSON is done outside the EDT.
Another area of major savings was in the view(GriffonWeatherServiceView.groovy). I was able to declare the functionality of a label/value pair and repeatedly invoke the defined closures.
import net.miginfocom.swing.MigLayout
application(title:'Griffon Weather', pack:true, size:[700,400], pack:true, locationByPlatform:true, layout:new MigLayout()) {
label("Airport Code:")
textField(id:'tf',columns:5, constraints:'spanx',
actionPerformed:{controller.getJSON('http://ws.geonames.org/weatherIcaoJSON?ICAO=K'+tf.text?.toUpperCase())
})
def createHorizontalBox = { fieldName, boundParam ->
label(fieldName, constraints:"newline")
label(id:fieldName.replace(' ','_')+'Label', foreground: java.awt.Color.BLUE,
text:bind(source:model, sourceProperty:boundParam), constraints:'wrap'
)
}
def createHorizontalBoxWithSuffix = { fieldName, boundParam, suffix ->
label(fieldName, constraints:'newline')
label(id:fieldName.replace(' ','_')+'Label', foreground: java.awt.Color.BLUE,
text:bind(source:model, sourceProperty:boundParam, converter:{it + suffix}), constraints:"wrap, spanx"
)
}
createHorizontalBox('Station: ', "stationName")
createHorizontalBox('Clouds: ', "clouds")
createHorizontalBoxWithSuffix('Wind Direction: ', "windDirection", " degrees")
createHorizontalBoxWithSuffix('Wind Speed: ', "windSpeed", " knots")
createHorizontalBoxWithSuffix('Temperature: ', "temperature", " C degrees")
createHorizontalBoxWithSuffix('Dew Point: ', "dewPoint", " C degrees")
createHorizontalBoxWithSuffix("Humidity: ", "humidity", "%")
createHorizontalBoxWithSuffix("Sea Level Pressure: ", "seaLevelPressure", "mb")
// createHorizontalBox("METAR Observation: ", "observation")
}
On startup, I declared a JXBusyLabel to live on our GlassPane and indicate when the system is busy. From Startup.groovy:
import java.awt.Dimension import java.awt.geom.* import org.jdesktop.swingx.JXBusyLabel import org.jdesktop.swingx.painter.BusyPainter def busyLabel = new JXBusyLabel(app.appFrames[0].getSize()) busyLabel.setOpaque(false) busyLabel.setBusy(false) busyLabel.getBusyPainter().setHighlightColor(java.awt.Color.DARK_GRAY) busyLabel.getBusyPainter().setPointShape(new Ellipse2D.Float(1,1,10,10)) app.appFrames[0].setGlassPane(busyLabel)

Comments
-
Jim Shingler
said:
Sweet! Where can we download the app
-
JW
said:
The code above was everything I had to write for the app. Wasn't planning on offering it as since I'm using pretty stock stuff(no resources). The busy label stuff requires the swingx-plugin and as a result Griffon 0.1-BETA or higher.
-
bladewheels
said:
For other n00bs like me just add the following JARs to a newGriffon project's lib directory and you should be able to run the code above:
commons-beanutils-1.8.0.jar
commons-beanutils-bean-collections-1.8.0.jar
commons-beanutils-core-1.8.0.jar
commons-collections-3.2.1.jar
commons-lang-2.4.jar
commons-logging-1.1.1.jar
commons-logging-adapters-1.1.1.jar
commons-logging-api-1.1.1.jar
ezmorph-1.0.5.jar
json-lib-2.2.3-jdk15.jar
miglayout-3.6.2.jar
swingx-2008_09_13.jar
blog comments powered by Disqus