Tuesday, January 5, 2010

FlexBuilder 3 and FlexUnit4 RC1

This will be a quick post on how to setup FlexBuilder project to use FlexUnit4. This post will be more prescriptive with just the steps without a lot of explanation. The FlexUnit site has good documentation.

1) Setup a Flex Project as you normally would.
2) Download the FlexUnit4 from this link.
3) Unzip and copy all of the swc files to your projects libs directory. There should be 4 swc files.
4) create a package/folder under the src directory called flexUnitTests. It could be called anything.
5) Create a test Suite class. This is just a POAO ( plain old actionscript object ). I called mine FlexUnit4Suite and it looked like the following:
1:  package flexUnitTests  
2: {
3: import org.flexunit.runners.Suite;
4: [Suite]
5: [RunWith("org.flexunit.runners.Suite")]
6: public class FlexUnit4Suite
7: {
8: public var testCase1:MyTestCase;
9: }
10: }


Lines 4 and 5 are the most interesting. Use the Suite and RunsWith metadata tags. Line 8 is a reference to your TestCase class. As you add more TestCase classes, just add a new variable here. You dont have to instantiate it, or anything - just declare it.

6) Create your TestCase class, which again is a POAO. Mine looked like the following:

1:  package flexUnitTests  
2: {
3: import flexunit.framework.Assert;
4: public class MyTestCase
5: {
6: public function MyTestCase()
7: {
8: }
9: [BeforeClass]
10: public static function runBeforeClass():void {
11: trace("runBeforeClass");
12: }
13: [AfterClass]
14: public static function runAfterClass():void {
15: trace("runAfterClass");
16: }
17: [Before]
18: public function runBeforeEveryTest():void
19: {
20: trace("runBeforeEveryTest");
21: }
22: [After]
23: public function runAfterEveryTest():void
24: {
25: trace("runAfterEveryTest");
26: }
27: [Test]
28: public function testTrue():void
29: {
30: trace("checkMethod");
31: Assert.assertTrue( true );
32: }
33: [Ignore("Not Ready to Run")]
34: [Test]
35: public function testNotReady():void
36: {
37: Assert.assertFalse( true );
38: }
39: }
40: }


7) Create a new Application for the test runner. You dont have to create a new application, but I assume you already have an Application class that really runs your application. I created another Application called FlexUnitTestRunner. It looked like the following:

1:  <?xml version="1.0" encoding="utf-8"?>  
2: <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"
3: xmlns:flexunitrunning="http://www.adobe.com/2009/flexUnitUIRunner"
4: minWidth="1024" minHeight="768" creationComplete="onCreationComplete()">
5: <mx:Script>
6: <![CDATA[
7: import flexUnitTests.FlexUnit4Suite;
8: import org.flexunit.listeners.UIListener;
9: import org.flexunit.runner.FlexUnitCore;
10: private var flexUnitCore:FlexUnitCore;
11: private function onCreationComplete():void {
12: flexUnitCore = new FlexUnitCore();
13: flexUnitCore.addListener(new UIListener( testRunner));
14: flexUnitCore.run(FlexUnit4Suite);
15: }
16: ]]>
17: </mx:Script>
18: <flexunitrunning:TestRunnerBase id="testRunner" width="100%" height="100%" />
19: </mx:Application>



Run the FlexUnitTestRunner application and my console output looked like:
1:  runBeforeClass  
2: runBeforeEveryTest
3: checkMethod
4: runAfterEveryTest
5: runAfterClass


and a browser window popped up with the results of my test case.

Thats it in a nutshell how to get a Flex Builder 3, sdk 3.5, FlexUnit4 up and running quickly.

Sunday, January 3, 2010

FlexORM

What seems like many years ago now, because it was - I saw a presentation by Christophe Coenraets where he talked about a simple ORM for AIR. You can see the original blog post here.

I have used Hibernate for many years, and now most recently GORM and I am a big fan of ORM tools when used right and in the right measure.

When I started looking at an AIR application I decided to revisit the work done in that original blog posting and I was very pleased to see that work had continued on with the ORM mapping, now called FlexORM. (Which is an interesting name since it only works with AIR ). You can find more information about the FlexORM tool here.

What I am going to do is present some examples of how I used it. I am hoping that my examples will help answer your questions on how to use the tool or inspire you start using it.

In my simple application my domain model is one where a Project can have sub-projects to form a project hierarchy and each project can have many TimeEntry references but a TimeEntry is only allocated to a single Project.

Lets look at the Project domain class first.
package com.redpointtech.domain
{
import mx.collections.ArrayCollection;
import mx.collections.IList;

[Bindable]
[Table( name="PROJECT")]
public class Project
{
// each project can contain a number of child or
// sub projects.
private var _subProjects:IList = new ArrayCollection();
private var _timeEntries:IList = new ArrayCollection();

[Id]
public var id:int;

[Column( name="proj_name")]
public var name:String;

[Column( name="proj_desc")]
public var desc:String;

[Column(name="color")]
public var color:Number;

[ManyToOne(name="parent_id", inverse="true")]
public var parent:Project;

[OneToMany(type="com.redpointtech.domain.Project", fkColumn="parent_id", lazy="false", cascade="save-update", indexed="true")]
public function set subProjects(value:IList):void {
_subProjects = value;
}
public function get subProjects():IList {
return _subProjects;
}
public function addSubProject(value:Project):void {
value.parent = this;
_subProjects.addItem(value);
}

[OneToMany(type="com.redpointtech.domain.TimeEntry", fkColumn="project_id", lazy="true", cascade="all", indexed="true")]
public function set timeEntries(value:IList):void {
_timeEntries = value;
}
public function get timeEntries():IList {
return _timeEntries;
}
public function addTimeEntry(value:TimeEntry):void {
value.project = this;
_timeEntries.addItem(value);
}


public function Project()
{
}

}
}


The primary metadata tags I used user:
[Table( name="tbd")] to define the table name for the domain object.

[Id] to tell the ORM which field is used for an id. As of right now the id value must be of type 'int'.

[Column( name="tbd")] to define the column name for a property. If the name field is not specified then it uses the property name.

[ManyToOne(name="col name", inverse="true/false")] to define the ManyToOne relationship with itself.

[OneToMany(...)] metadata tag is specific on the set/get methods. Also noticed that we added an 'add' method which sets the parent of the Project and adds the Project to the collection of sub projects.

The TimeEntry class looks like the following:
package com.redpointtech.domain
{
import com.redpointtech.util.Constants;

[Bindable]
[Table( name="TIMEENTRY")]
public class TimeEntry
{

[Id]
public var id:int;

[Column( name="full_year")]
public var fullYear:int;

[Column( name="month")]
public var month:int;

[Column( name="day_of_month")]
public var dayOfMonth:int; // Date.date equivalent

[Column( name="start_hour")]
public var startHour:int;

[Column( name="start_min")]
public var startMin:int;

[Column( name="end_hour")]
public var endHour:int;

[Column( name="end_min")]
public var endMin:int;

[Column( name="notes")]
public var notes:String="";

[Column( name="summary")]
public var summary:String="";

[ManyToOne(name="project_id", inverse="true")]
public var project:Project;

public function TimeEntry()
{
}

public function setDate(date:Date):void {
fullYear = date.fullYear;
month = date.month;
dayOfMonth = date.date;

}
public function setStartTime( hour:int, min:int):void {
startHour = hour;
startMin = min;
}
public function getStartDate():Date {
var startDate:Date = new Date(fullYear, month,dayOfMonth,startHour,startMin);
return startDate;
}

public function setEndTime(hour:int, min:int):void {
endHour = hour;
endMin = min;
}
public function getEndDate():Date {
var endDate:Date = new Date(fullYear,month,dayOfMonth,endHour,endMin);
return endDate;

}
private function _getTimeDiffInMillis():Number {
var startDate:Date = getStartDate();

var endDate:Date = getEndDate();

var diff:Number = endDate.getTime() - startDate.getTime();

return diff;
}

[Transient]
public function get elapsedHours():Number {
var diff:Number = _getTimeDiffInMillis();

var hours:Number = diff / Constants.millisecondsPerHour;

var floorHours:Number = Math.floor(hours);


return floorHours;
}

[Transient]
public function get elapsedMinutes():Number {
var hours:Number = elapsedHours;
var milliHours:Number = hours * Constants.millisecondsPerHour;

var diff:Number = _getTimeDiffInMillis();

var minuteDiff:Number = diff - ( milliHours );

var minutes:Number = minuteDiff / Constants.millisecondsPerMinute;
var roundedMinutes:Number = Math.min(Math.round(minutes),59);
return roundedMinutes;

}

}
}


The only real difference is the use of Transient to tell FlexORM that these are not used in the persistence of the object.

Now that we have a feel for how to annotate our domain classes to be used by FlexORM, lets see how to persist them to the database.

I am using FlashBuilder Beta2 with FlexUnit4 RC1. I will go over the integration of FlashBuilder and FlexUnit in another blog posting. For this one I wanted to remain focused on using FlexORM.

Below is a unit test function to test Project CRUD ( create read update delete )

    [Test]
public function testCRUDNoHier():void {
trace("----------------testCRUDNoHier----------------");
var em:EntityManager = EntityManager.instance;

var p:Project = new Project();
p.name="P1";
p.desc = "proj from unit test";
p.color=0xFF001E;
em.save(p);

Assert.assertEquals("P1",p.name);

var p2:Project = new Project();
p2.name="P2";
p2.desc = "proj2 from unit test";
p2.color=0xAAAAAA;
em.save(p2);

Assert.assertEquals("P2",p2.name);
Assert.assertEquals("P1",p.name);

var p1:Project = em.loadItem(Project, p.id) as Project;
Assert.assertEquals("P1",p1.name);

var ps:ArrayCollection = em.findAll(Project);
Assert.assertEquals(2,ps.length);

p1.desc = "New P1 desc";
em.save(p1);

var p3:Project = em.loadItem(Project,p1.id) as Project;
Assert.assertEquals("New P1 desc", p3.desc);

em.remove(p2);
ps = em.findAll(Project);
Assert.assertEquals(1,ps.length);


}



We start the test method by getting a reference to the EntityManager via EntityManager.instance. The first thing we do in the test case, is to create a Project and the save it to the DB via the em.save(p).

That is all you have to do to save an entity, you do not need to write any sql yet.

To load an item for which you have the id, you use the loadItem method passing in the Project class and the id value.

To find all records for a particular class, you use the findAll method.

FlexORM even has a Criteria API. While it is not on the same level as the Hibernate Criteria, it is still very useful.

Below is an example method to find TimeEntries on a particular day:

    public static function findAllOn(fullYear:Number,month:Number=-1,dayOfMonth:Number=-1):ArrayCollection {
var teCriteria:Criteria = entityManager.createCriteria(TimeEntry);
teCriteria.addEqualsCondition("fullYear", fullYear);
if( month > -1 ) teCriteria.addEqualsCondition("month",month);
if( dayOfMonth > -1 ) teCriteria.addEqualsCondition("dayOfMonth",dayOfMonth);
teCriteria.addSort("startHour");

return entityManager.fetchCriteria(teCriteria);
}



You use the EntityManager to create the criteria for an annotated class. In this example I am using simple conditions to see that the TimeEntry has values that equal those passed in, and then sort the returned values based on the starting hour.

Again - so far I have created no sql myself. I am sure I will have a case where I need to hand craft some sql, and this will be fairly easy even with the EntityManager.

The idea behind the ORM tools, IMHO, is not to take over all of the persistence work but instead take over the mundane and tedious persistence work so we can concentrate on the more difficult tasks. So far FlexORM has done that.

I hope this quick example was useful in getting you interested in looking at FlexORM.

Next time I will discuss how I used FlashBuilder Beta2 and FlexUnit to test the application.

Tuesday, September 29, 2009

Designing the Obvious

Adobe MAX2009 is coming up this weekend, and while I won't get the chance to go this year I was reviewing some of the material from years past. One presentation that I have somehow hung onto was one by Robert Hoekman Jr on 'Designing the Obvious' - from his book of the same name.

I never got the book - but I think I will now. As I was reading through the paper, he talks about something we all still come across everyday - form fields with character length limits.

He mentions some of the 'obviously' wrong ways to do this:
1) type in the field, hit submit and the system tells you that you guess wrong and that there were too many characters in the field.
2) There is a 'hint' next to the text field but it lets you type in the wrong number of characters anyways. He also noted that a 'hint' is not instructive to tell someone that spaces are or are not counted.
3) The field just lets you type in characters until it just stops. The 'stops' feedback still leaves us to wonder why the system stopped taking input.

The 'obvious' answer he suggests is to give the user real-time feedback about their input. As his book title suggests - this is so obvious.

I thought I would create a quick component that did just that - gave the user real-time feedback on their input and accounted for either including space or excluding space.

In the example application below, the first input just stops taking input at the maximum character - can you guess what that limit might be? The second input uses a StringValidator - which is a step in the right direction, but does not provide the real-time feedback.

The next two use a simple component I created. The first of those, has a 15 character limit, and spaces count toward that limit. The next one has a 30 character limit and spaces do not count toward the limit.

Getting the component to ignore spaces was trickier than I thought it would be at first - but for those curious you can download the code and see how I did this.

Source can be found by clicking here



Thanks for stopping by. I would love to hear your comments on the 'obvious'.

Saturday, May 23, 2009

Converting a Java/Flex application to Grails/Flex

I have a small reference application to CRUD ( create, read, update and delete ) notes from a back end service using a simple flex application. This reference application was originally written with Spring 2.5, later upgraded to 3, and Hibernate 3.x with MySql 5. As new versions of these technologies come out, I integrate it into this simple reference application. I use it as a sanity check while integrating all of those technologies because the application is not too difficult.

I decided to try to migrate it to a Grails platform to get a sense for how difficult that might be. I am going to share the steps that I took to migrate my Tomcat/Spring3/Hibernate3/MySql5 application to Grails/MySql5.

Since I already had a database schema from the Java implementation I decide to leverage that DDL so that both applications could run against the same backend.

Since I knew that the user interface was a Flex interface, I installed the flex plugin.

grails install-plugin flex
This plugin allows one to expose a Grails service as a Flex BlazeDS service by simply adding: static expose = ['flex-remoting'] to the service.

The 3 methods that the Java service exposed were:
* getAllNotes
* saveOrUpdateNote
* deleteNode

Therefore, to maintain the same Flex UI I needed to expose the same API.

The service ultimately ended up looking like the following:
package com.redpointtech.services
import com.redpointtech.domain.Note
class RedpointNotesService {

boolean transactional = true
static expose = ['flex-remoting']

def getAllNotes() {
def allNotes = Note.findAll()
return allNotes
}

def saveOrUpdateNote(Note note) {
def noteid = note.id
println "NOTE ID TO SAVE: " + noteid
// until I can figure out how to get unsavedValue:0 to
// work I need to do my own unsaved value.
if( noteid == 0 ) {
note.id = null;
}
note.save()
}

def deleteNote(Long noteId) {
def note = Note.findById(nodeId)
note?.delete()
}
}



Take special note of the check for a zero noteid. I could not immediately figure out how to tell grails that the 'unsaved-value' is 0 instead of the default null, so I had to make that programmatic. I am still looking into this, but if you know the answer please leave a comment.

To my pleasant surprise, the service was very small and the only extra work to expose the service to Flex was to add the 'static expose = ['flex-remoting']'. Anyone that has exposed Flex services knows that you have to add a servlet to the web.xml, update the flex-remote.xml, add a Spring factory to get the service. While not hard, certainly more work that added one line to a service. Please note, that I did NOT update the web.xml at all.

Next I turned my attention to the Note domain object. I used the the grails create-domain command to create the Note domain object.

To keep this as minimal as possible to start I did not add any constraints - clearly you would normally.

The Note domain class just keeps track of the authors name, the text of the note and the date the note was created. There is no mapping to a User domain object because I purposefully wanted to keep this reference application very simple.

Below is the Grails domain object:
package com.redpointtech.domain
class Note{

//Long id
String author
String text
Date createDate

static constraints = {
}

static mapping = {
table 'notes'
version false
id unsavedValue: 0
text column: "note_text"
createDate column: "create_date"
}

}



There are a couple of aspects to note:
* I commented out the id property because by convention Grails gives every domain object an id property.
* I had to add a mapping section because I was not able to use the grails default values.

If you look up at the service class again, you will see methods on the Note class like 'findAll' but you do not see them here. This is because GORM, which is Grails' Object Relational Mapping which sits on top of hibernate, creates many of the standard methods for you and dynamically adds them at runtime. This dramatically decreases the number of DAO methods you need to write - and really all but eliminates the DAO layer all together.

At this point, I now I have a service and a domain object that represents the Notes. Because I am using a MySql database and not the supported out of the box HSQL DB, I have a little more configuration to do.

The grails-app/conf/DataSource.groovy file contains the datasource definitions for all of the environments.

Below is the version of DataSource.groovy that I used:
dataSource {
pooled = true
driverClassName = "org.hsqldb.jdbcDriver"
username = "sa"
password = ""
}
hibernate {
cache.use_second_level_cache=true
cache.use_query_cache=true
cache.provider_class='com.opensymphony.oscache.hibernate.OSCacheProvider'
}
// environment specific settings
environments {
development {
dataSource {
pooled = false
driverClassName = "com.mysql.jdbc.Driver"
dbCreate = "update" // one of 'create', 'create-drop','update'
url = "jdbc:mysql://localhost/test"
username = "root"
password = "root"
logSql="true"
dialect = org.hibernate.dialect.MySQL5InnoDBDialect
}
hibernate {
show_sql = true
hibernate.format_sql = true
}
}
hsqldevelopment {
dataSource {
dbCreate = "create-drop" // one of 'create', 'create-drop','update'
url = "jdbc:hsqldb:mem:devDB"
}
}
test {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:mem:testDb"
}
}
production {
dataSource {
dbCreate = "update"
url = "jdbc:hsqldb:file:prodDb;shutdown=true"
}
}
}

You can see in the environments section, I altered the development datasource to use MySql instead of HSQL. This should be pretty standard datasource configuration for MySql.

The last thing I needed to do was drop the mysql JDBC jar into the lib directory - and it was complete.

In the end I went from absolute zero, to a working Grails application that gave me the same functionality as my Tomcat/Spring/Hibernate application in less than an hour. I have to say, I was very impressed with the speed of development in the grails environment.

My company is currently working on a full featured, consumer facing Flex/Grails application and Grails has still not let me down even in this more challenging real world environment.

Every time I use Grails, I am more convinced of its viability as a production ready framework. It probably helps that it is built upon production ready frames of Spring and Hibernate.

Sunday, March 29, 2009

Creating a Grails Plugin for DBUnit

A colleague of mine Eric Weimer introduced me to Grails. I had already used Groovy to a limited degree but not Grails. Eric presented at the Chicago Groovy Users Group and I really wanted to learn more about Grails.

So I bought the book The Definitive Guide to Grails - 2nd Edition which I highly recommend. After getting through nearly all of the book, I really wanted to start working with Grails, so I took the example from the book and extended the Domain model slightly. The area that I wanted to focus on for now was to get a better understanding of GORM, which is the ORM layer that uses Hibernate.

To do this I created a simple Grails application with just Domain classes and I started to create some integration tests that I could use to get a feel for the GORM api.

However, to do this I quickly ended up needing to seed the database with some data so I could execute queries. In my Java bag of tricks, I have used DBUnit and I immediately wanted to leverage this in my Grails work. Normally I would have checked to see if there was already a DBUnit Grails plugin, but I was actually in 'a dead zone' - and did not have internet access. But this was ok - I already had all of the DBUnit code I needed from my Java projects.

What this blog posting is going to show is how I used the information from the Grails book to create my own DBUnit plugin that I can now leverage in my projects. As it turns out, there is already a DBUnit plugin and I will either submit my version or work with the author of that plugin to see about integrating some of my features into his code base. This is my first venture into Grails - so if someone out there sees a better way to tackle this kind of problem please post a comment so we can all benefit.

Step 1 - create a plugin application
grails create-plugin dbunit

This will create what looks like a typical Grails application. The only real difference is a file in the root directory of the project called: DbunitGrailsPlugin.groovy

Step 2 - Update DbunitGrailsPlugin.groovy

Besides the author,title, etc information the only section I changed was the doWithSpring closure. I decided to take the utility class that I already had for my Java development, and create a Spring bean out of it. This Spring bean can then be used in the Grails integration tests. Below is the updated doWithSpring closure.

def doWithSpring = {
dbunitUtil(com.redpointtech.dbunit.DBUnitUtil) { bean ->
bean.scope = "prototype"
}
}


What this says is to create a bean with the name dbunitUtil of the type com.redpointtech.dbunit.DBUnitUtil and set the scope to prototype. Because my DBUnitUtil does keep track of some DBUnit artifacts, I decided to require a new instance to avoid any thread synchronization issues.

Step 3 - Add my Java class DBUnitUtil.java to Grails src
In the root directory of the Grails plugin application, there is a folder called 'src'. In that folder there is another called 'java', and in there I placed the com.redpointtech.dbunit.DBUnitUtil.java class. Since this post is not about the DBUnit part, I am not going to go into the details of that class but it is included in the source distribution at the end of this blog post.

Step 4 - Add DBUnit jars to the lib dir
In the root directory of the Grails plugin application, there is a lib folder and in there I placed the dbunit-2.4.4.jar file.

Step 5 - package the plugin
Because I have not applied for contributor access, I cannot release the plugin so for now I can only package up the plugin and make it available locally. To package the plugin you use the command from the root of the plugin project:

grails package-plugin

This created a file called gails-dbunit-0.1.zip

Thats it - I now have a plugin I can use in any grails application to help support integration testing.

On a side note - Grails supports unit tests and integration tests. Unit tests are supposed to be very lightweight and will not initialize all of the database infrastructure so you have to create integration tests to test the

I created another application that I called gormtest and I used many of the Domain classed from the Album/Song/Artist example from the Grails book.

To use the new plugin you can install a plugin directly from the zip file using the following command:

grails install-plugin [full path to the gails-dbunit-0.1.zip]

Doing this makes a Spring available to my Integration tests. I created an itegration test using the following:

grails create-integration-test com.redpointtech.gtunes.Album

This created a file called AlbumTests.groovy in the test/integration/com/redpointtech/gtunes directory.

An abbreviated version of the AlbumTests.groovy file follows:

1 package com.redpointtech.gtunes
2
3 import grails.test.*
4 import com.redpointtech.dbunit.*
5 import javax.sql.DataSource
6
7 class AlbumTests extends GrailsUnitTestCase {
8 DataSource dataSource
9 DBUnitUtil dbunitUtil
10
11
12 protected void setUp() {
13 super.setUp()
14 dbunitUtil.onSetup(dataSource, "./data/user-testdata.xml, ./data/album-testdata.xml")
15 }
16
17 protected void tearDown() {
18 super.tearDown()
19 dbunitUtil.onTearDown()
20 }
21
22 void testAlbumFind() {
23 def albums = Album.findAllByTitleLike("title1%")
24 assertEquals albums.size, 3
25 albums.each { album ->
26 println "------------------------------------"
27 println "Title: " + album.getTitle()
28 def artist = album.getArtist()
29 println "Artist: " + artist.getName()
30 }
31 }
32}



On lines 8 and 9, we create two instance variables that Grails will automatically inject the beans into - following the naming convention. The first is the dataSource used for the test and the second is the DBUnitUtil bean that is provided by the Plugin we just created.

On line 14, we use the DBUnit plugin bean, to load the database referenced by the dataSource, with the data in the two XML files.

On line 19, we use the dbunitUtil bean again to tear down the data used for the test. I know someone is going to ask about the time it takes to load and unload all of the data for each test and it is a fair concern. My suggestion is to group as many tests in the single test method as is practical, and since I dont think anyone would really want to create dependencies between their tests you probably do want to start with a fresh data load for the next test.

On line 23, we can then start to use the GORM API, against the data that was loaded by the DBUnit plugin.

The last part to take care of is the XML data files. I placed the data files in the grails-app/conf directory in a subdirectory called data

The user-testdata.xml file looks like the following:
<?xml version="1.0" encoding="UTF-8" ?>

<dataset>
<user id="1000" version="1" first_name="user1_fn" last_name="user1_ln" user_name="user1_un" />
<user id="2000" version="1" first_name="user2_fn" last_name="user2_ln" user_name="user2_un" />
<user id="3000" version="1" first_name="user3_fn" last_name="user3_ln" user_name="user3_un" />
<user id="4000" version="1" first_name="user4_fn" last_name="user4_ln" user_name="user4_un" />


</dataset>



That is pretty much all there is to creating a simple DBUnit plugin, that leveraged the Java code I already had to pre-populate a test database with data so I could start to get familiar with the GORM api through the integration tests.

The source code for both the dbunit plugin and the gormtest application can be found here:

DBUnit Grails Plugin

Gormtest Grails App

Sunday, March 15, 2009

How to create a popup window from an ItemEditor

I was recently helping a client that needed to create an item editor that would allow them to update the text field of a data grid, which also rendered an Icon that the user could click to create a comment line. Clicking the comment Icon would bring up a popup window to edit the comments. When the popup window was dismissed, the UI should show the ItemEditor in a state to allow the user to complete the editing of the original DataGrid item.

In their initial implementation they did not use the ItemEditEnd event from the DataGrid, because they noticed that when the popup window was created the ItemEditEnd event would fire and Flex would go through the entire process of ending the editing session. Clearly not what they wanted. The client went through a number of 'workarounds' but they always avoided the ItemEditEnd event.

I think trying to circumvent the cell editing process is almost always a bad idea that leads to code that will break as new releases of the Flex framework come out, and will certainly confuse developers that have to maintain the code. So I set off trying to create a simplified example of two options for them, one of which was a popup window. Since it was sufficiently tricky - I thought there may be others out there that would benefit from this example.

Below is a working example of what I put together. In the top DataGrid, I setup the comment editor to NOT use a popup window and instead just toggle between a textinput to edit the cell data, and a textinput to edit the comment line. Personally I find the popup window awkward to the user experience - but that was the client requirement. The second DataGrid is a simplified version of the required popup window. Notice that it only allows for a single line and takes the return key as the single to end editing the comment.

You can find the code for this example here

Tuesday, February 3, 2009

Flex ItemRenderers - and the case of the random renderering

I was reading through the book 'Flex 3 in Action' and I came across what seemed like a simple ItemRenderer example that I thought would be good show some people because it demonstrated what not to do, the visual consequences and then how to fix it. Except - in my testing it did not fix it.

The purpose of the blog posting is to highlight the issues and how I ultimately solved it - albeit I don't fully understand yet why it was happening. That will take a little more time.

The example was a simple one. There is a data grid with a data provider of objects and if the object has an email address show a button in the grid that the user can press to send the person an email. If the object in the dataprovider has no email address do not show the button.

This example is taken from section 9.2.3, page 190 of Flex 3 In Action.

Below is a working example from that section. In the first data grid, the ItemRenderer was incorrectly implemented just as the authors suggest. That being during creationComplete event we check the data to see if we should render the email button. This is incorrect, because Flex will reuse ItemRenderers and the creationComplete method should only be called once.

In the 'correct' version, we look for the dataChange event and then run the logic again to see if we should render the email button. However, I noticed that if I scrolled the list to the bottom, and then scrolled up via the scroll button one line at a time, the first time Mikey Mouse did not get a button. For some reason, the creationComplete was called for that ItemRenderer - even though the ItemRenderer that was there had its dataChange event called. Or at least that is how it appeared. Why this is happening I am not sure. However, to fix this problem I changed the creationComplete to call the checkEmail function just like dataChange and enhanced the error checking to make sure we cover the case where the ItemRenderer is created before the data property is set.

Now in the third data grid, you can scroll to the bottom, and then back up and see that Mikey Mouse can now still receive email...

Source can be found by clicking here

If anyone knows why the example exercise did not work, please leave a comment.