
We were playing internally with a Flex project and after our first Scrum sprint we ended up with a real nice looking frontend, ... but also with Flex Spaghetti! All kind of events were bubbling up and down through the application.
After searching the internet I noticed a lot of people are having the same problems. Using a framework or a blueprint architecture should help us. In Java we have a lot of frameworks to choose from, each with it's advantages and disadvantages. On the Flex side the number of mature frameworks is limited. For me this is a sign that (professional) Flex development is in it's early stages. However there are some frameworks. Luke Bayes and Ali Mills made a nice comparison of the available frameworks. Cairngorm and PureMVC scored best in this comparison and so I tried them both. I prefer PureMVC as it's much cleaner using the Mediator pattern. This allows to send event backs to the view eaily and clean as well.
In my free time I also started to experiment a little bit with AIR and it's embedded SQLite database engine. This is useful for storing local data in offline mode but also for creating small stand-alone applications like a personal Movie database. When talking to a database I'm used (and spoiled ;-) to Grails ORM (GORM) or Spring's JDBCTemplate and HibernateTemplate. There is nothing like this available in Flex/ActionScript. To write GORM in ActionScript is not an option, but to port Spring's JdbcTemplate to a SqliteTemplate in ActionScript was doable on a nice summer evenning. I'm currently in talks with Cliff Hall of the PureMVC project to add this as an utility to the PureMVC project. If you are interested in in the meantime, just leave your email address and I will send you a copy.
Wednesday, July 30, 2008
Flex Spaghetti
Wednesday, July 9, 2008
Grails for creating quick mock applications

Currently I'm working on an Adobe Flex front-end which will communicate to Microsoft Team Foundation Server using some SOAP webservices.
As we are currently just concentrating on the Flex front-end, there are no TFS webservices available yet. This makes testing the Flex front-end somehow difficult. To make things easy we wanted to create some mock TFS webservices. This is when Grails comes is!
Within the hour we had the mock application running. Creating some basic domain classes, installing the xfire plugin and creating some services exposed as xfire webservices. And for verifying the data is stored correctly in the database we just used the generate-all command to create some basic CRUD views.
I know the Grails mock application will not see the production light, but creating quick mock applications for temporary usage is another thing in which Grails can be used very effectively.
Posted by
Marcel Overdijk
at
11:12 PM
2
comments
Monday, June 16, 2008
Grails YUI DataTable example

After I quit looking into the Ext JS library because of the license problems, I wanted to use another AJAX framework which comes bundled with nice widgets. Before I started with Ext I had already looked into The Yahoo! User Interface Library (YUI) but preferred Ext that time. So switching back to YUI makes sense... The only reason I was hesitating was because of Yahoo's possible takeover by Microsoft as it would be uncertain what would happen to YUI on the short and long term. The news that the deal between Microsoft and Yahoo seems to be off the table definitely, made my choise to use YUI easy.
Today I created an example on how to use YUI's DataTable within a Grails application.
The example is based on this simple Country domain class:
class Country {
String country
}
The Grails CountryController looks like:
class CountryController {
def index = { redirect(action: list, params: params) }
def list = {
}
def listData = {
def countryList = Country.list(params)
response.setHeader("Cache-Control", "no-store")
render(contentType: "text/json") {
totalRecords(Country.count())
records {
for (r in countryList) {
country(id: r.id, country: r.country)
}
}
}
}
}
As you can see the list action is doing nothing except rendering the list.gsp. The listData action will be called by the YUI DataTable and returns the records in JSON format together with the total count of records (totalRecords). So at last - and a big chunk of JavaScript code - we need the list.gsp to create and display the YUI DataTable:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
<meta name="layout" content="main" />
<title>Country List</title>
</head>
<body>
<div id="demo">
<div id="paging"></div>
<div id="dt"></div>
</div>
<script type="text/javascript">
// Set up DataSource
var myDataSource = new YAHOO.util.DataSource('${createLink(action: 'listData')}?');
myDataSource.responseType = YAHOO.util.DataSource.TYPE_JSON;
myDataSource.responseSchema = {
resultsList : 'records',
fields : ['id', 'country'],
metaFields : {
totalRecords: 'totalRecords'
}
};
// A custom function to translates sorting and pagination values
// into a query string the server will accept
var buildQueryString = function (state,dt) {
return "offset=" + state.pagination.recordOffset +
"&max=" + state.pagination.rowsPerPage +
"&sort=" + state.sorting.key +
"&order=" + ((state.sorting.dir === YAHOO.widget.DataTable.CLASS_ASC) ? "asc" : "desc");
};
// Custom function to handle pagination requests
var handlePagination = function (state,dt) {
var sortedBy = dt.get('sortedBy');
// Define the new state
var newState = {
startIndex: state.recordOffset,
sorting: {
key: sortedBy.key,
dir: ((sortedBy.dir === YAHOO.widget.DataTable.CLASS_DESC) ? YAHOO.widget.DataTable.CLASS_DESC : YAHOO.widget.DataTable.CLASS_ASC)
},
pagination : { // Pagination values
recordOffset: state.recordOffset, // Default to first page when sorting
rowsPerPage: dt.get("paginator").getRowsPerPage()
}
};
// Create callback object for the request
var oCallback = {
success: dt.onDataReturnSetRows,
failure: dt.onDataReturnSetRows,
scope: dt,
argument: newState // Pass in new state as data payload for callback function to use
};
// Send the request
dt.getDataSource().sendRequest(buildQueryString(newState), oCallback);
};
var myColumnDefs = [
{key: "id", label: "Id", sortable: true},
{key: "country", label: "Country", sortable: true}
];
var myTableConfig = {
initialRequest : 'max=10&offset=0&sort=id&order=asc',
generateRequest : buildQueryString,
paginator : new YAHOO.widget.Paginator({containers: "paging", rowsPerPage: 10}),
paginationEventHandler : handlePagination,
sortedBy : {key: "id", dir: YAHOO.widget.DataTable.CLASS_ASC}
};
var myDataTable = new YAHOO.widget.DataTable('dt', myColumnDefs, myDataSource, myTableConfig);
// Override function for custom server-side sorting
myDataTable.sortColumn = function(oColumn) {
// Default ascending
var sDir = "asc";
// If already sorted, sort in opposite direction
if(oColumn.key === this.get("sortedBy").key) {
sDir = (this.get("sortedBy").dir === YAHOO.widget.DataTable.CLASS_ASC) ? "desc" : "asc";
}
// Define the new state
var newState = {
startIndex: 0,
sorting: { // Sort values
key: oColumn.key,
dir: (sDir === "desc") ? YAHOO.widget.DataTable.CLASS_DESC : YAHOO.widget.DataTable.CLASS_ASC
},
pagination : { // Pagination values
recordOffset: 0, // Default to first page when sorting
rowsPerPage: this.get("paginator").getRowsPerPage()
}
};
// Create callback object for the request
var oCallback = {
success: this.onDataReturnSetRows,
failure: this.onDataReturnSetRows,
scope: this,
argument: newState // Pass in new state as data payload for callback function to use
};
// Send the request
this.getDataSource().sendRequest(buildQueryString(newState), oCallback);
};
</script>
</body>
</html>
The Grails' createLink taglib is used to define the url to retrieve the JSON data from. For the rest, the YUI DataTable configuration and paginator take care of all server side paging and sorting.
Note that I included the following YUI javascript and css files in my main.gsp:
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/reset-fonts-grids/reset-fonts-grids.css">
<link rel="stylesheet" type="text/css" href="http://yui.yahooapis.com/2.5.2/build/assets/skins/sam/skin.css">
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/utilities/utilities.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/calendar/calendar-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/datasource/datasource-beta-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/datatable/datatable-beta-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/container/container_core-min.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/2.5.2/build/menu/menu-min.js"></script>
Off course it's also possible to install the Grails YUI plugin if you don't want to use the hosted files.
PS: a little bit of refactoring to make it more re-usable, and it would not be difficult to use in the scaffolding generation.
Posted by
Marcel Overdijk
at
8:01 AM
8
comments
Thursday, June 12, 2008
Grails at LinkedIn

Today Dmitriy Kopylenko posted a link to The LinkedIn Blog on the Grails user mailing list which features a new blog series about Grails. The first post contains a presentation with their experience using Grails to build applications. Their conclusion is that Grails is more productive than the current crop of mainstream Java web application frameworks and that Grails can work well in an Enterprise environment.
This is a must read for Grails adepts who are trying to convince their managers to use Grails. If LinkedIn is using it, why can't your company use it?
Posted by
Marcel Overdijk
at
11:13 AM
0
comments
Tuesday, May 27, 2008
Ext plugin discontinued

I just want to mention that I decided to discontinue working on the Ext (Scaffolding) plugin for Grails.
The reason is Ext's recent license change (1, 2, 3, 4) to GPL v3. Although the Ext team has announced some Open Source FLOSS exceptions, for me it's end of the line here Ext concerned.
Posted by
Marcel Overdijk
at
11:25 AM
7
comments
Monday, May 12, 2008
Uber Generate All script for Grails

While I was creating a Grails application on an exsiting database schema I was to lazy to run grails generate-all 15 times. So I created an Uber Generate All script, which generates controllers and views for all domain classes in your application.
If you want to use it create a new script within your application (e.g. grails create-script UberGenerateAll) and copy in the code below:
import org.codehaus.groovy.grails.commons.GrailsClassUtils as GCU
import groovy.text.SimpleTemplateEngine
import org.codehaus.groovy.grails.commons.spring.GrailsRuntimeConfigurator;
import org.codehaus.groovy.grails.scaffolding.*
import org.springframework.mock.web.MockServletContext
import org.springframework.core.io.Resource;
Ant.property(environment:"env")
grailsHome = Ant.project.properties."environment.GRAILS_HOME"
includeTargets << new File ( "${grailsHome}/scripts/Package.groovy" )
generateViews = true
generateController = true
target('default': "The description of the script goes here!") {
depends( checkVersion, packageApp )
typeName = "Domain Class"
doStuff()
}
target(doStuff: "The implementation task") {
rootLoader.addURL(classesDir.toURL())
def beans = new grails.spring.WebBeanBuilder().beans {
resourceHolder(org.codehaus.groovy.grails.commons.spring.GrailsResourceHolder) {
this.resources = "file:${basedir}/grails-app/domain/**/*.groovy"
}
grailsResourceLoader(org.codehaus.groovy.grails.commons.GrailsResourceLoaderFactoryBean) {
grailsResourceHolder = resourceHolder
}
def pluginResources = [] as Resource[]
if(new File("${basedir}/plugins/*/plugin.xml").exists()) {
pluginResources = "file:${basedir}/plugins/*/plugin.xml"
}
pluginMetaManager(org.codehaus.groovy.grails.plugins.DefaultPluginMetaManager, pluginResources)
grailsApplication(org.codehaus.groovy.grails.commons.DefaultGrailsApplication.class, ref("grailsResourceLoader"))
}
appCtx = beans.createApplicationContext()
appCtx.servletContext = new MockServletContext()
grailsApp = appCtx.grailsApplication
grailsApp.initialise()
def domainClasses = grailsApp.domainClasses
if(!domainClasses) {
println "Domain classes not found in grails-app/domain, trying hibernate mapped classes..."
try {
def config = new GrailsRuntimeConfigurator(grailsApp, appCtx)
appCtx = config.configure(appCtx.servletContext)
}
catch(Exception e) {
println e.message
e.printStackTrace()
}
domainClasses = grailsApp.domainClasses
}
if(domainClasses) {
def generator = new DefaultGrailsTemplateGenerator()
domainClasses.each { domainClass ->
if(generateViews) {
event("StatusUpdate", ["Generating views for domain class ${domainClass.fullName}"])
generator.generateViews(domainClass,".")
}
if(generateController) {
event("StatusUpdate", ["Generating controller for domain class ${domainClass.fullName}"])
generator.generateController(domainClass,".")
}
event("StatusUpdate", ["Finished generation for domain class ${domainClass.fullName}"])
}
event("StatusFinal", ["Finished generation for domain classes"])
}
else {
event("StatusFinal", ["No domain class found"])
}
}
Then just run grails uber-generate-all and watch!
I have only tested it for Groovy domain classes, but I think it will also work for Hibernate mapped classes. Enjoy.
Posted by
Marcel Overdijk
at
12:56 PM
5
comments
Labels: Grails
Sunday, May 4, 2008
Ref Code plugin

Back in my days as Oracle Designer developer we had a nice CG_REF_CODES table in which we could store (semi)-static domains. You can think about these domains as simple code/description lookup tables; for example OrderStatus, OrganizationType, MaritalState etc. As these lookups only contained codes and descriptions it's not worth creating specific tables. That's why we had this CG_REF_CODES tables; we stored all these simple domains, and it's allowable values in 1 simple table.
As this is convienient for any application I created a simple Grails plugin which provides the same functionality. You can store all your simple reference codes in a single table, and use it to render dropdown boxes or validate user input in custom constraints. Have a look at http://grails.org/Ref+Code+Plugin.
Posted by
Marcel Overdijk
at
1:29 PM
4
comments
Labels: Grails
