Griffon Weather App

16 December, 2008    
Written by

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

  1. Jim Shingler said:

    Sweet! Where can we download the app
  2. 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.
  3. 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