Sunday, December 31, 2006

Setting up Db2 v9 as a Coldfusion Datasource

In the last few weeks I've had the opportunity to get to know Coldfusion just a little bit better. One of the first challenges I faced is getting CF talking to Db2, so I could take advantage of the CF Extensions for Flexbuilder and that funky Wizard. To be precise I used ColdFusion MX 7.02 and DB2 Community Express Edition v9.1

This doesn't just apply to Flex, but maybe others might find this useful. Keep in mind I have only tested this in Windows:

  1. Install the db2 client or get access to a machine that does. I installed db2 in the default folder on my dev machine to get this working.
  2. Copy C:\Program Files\IBM\SQLLIB\java\db2java.zip to C:\CFusionMX7\lib. Important: Make sure db2jdbc.dll is in the path somewhere or copy it to same folder!
  3. Restart Coldfusion MX. Login to CF Admin. Go to Datasources.
  4. Add new Datasource:
  • driver type Other
  • CF DataSource Name: [sourcename]
  • JDBC url: jdbc:db2:[dbname]
  • Driver Class: COM.ibm.db2.jdbc.app.DB2Driver
  • Driver Name: DB2Driver
  • username: [name]
  • password: [password]
  • [Submit]
Couple things to note. Out of the blue the other day suddenly I started getting this db2jdbc not found in java path error... I figure something I installed hooped my system path. Instead of messing around with why, I figured out that just copying the dll into my CF lib folder fixed the problem. The ...app.DB2Driver is specific to Windows OS I believe. The username and password are usually a Windows account... probably best to use the account that has admin rights to db2 at least until you get it working.

I also did spend a good chunk of time trying to get the built in driver to talk to db2... I personally had no luck whatsoever, and I have read in other places that the drivers generally are only backwards compatible... the driver that ships with CF is definitely not v9, I think it might be v7... not sure though. I did test the backward compatibility claim by using the v9.1 jdbc driver against a db2 v8.x datasource and it worked fine.

DoubleClickEnabled & other little Flex tips

Want to code against double clicks in Flex? Don't forget to set this property to true!

Adding child objects to a canvas or some other container and need to keep the scroll moving down so users can see the new objects? Put this in a function:
canvas.verticalScrollPosition = canvas.maxVerticalScrollPosition + 45;
And remember to CallLater, this gives Flex a chance to lay the new object out and get you the right maxVerticalScrollPosition!

Are those child objects some kind of data entry control? Attach an event listener to the event
FocusEvent.FOCUS_IN for the object and point it to a function that does this:
private function test(event:FocusEvent):void{
canvas.verticalScrollPosition = event.currentTarget.y - canvas.height + 45;
}


Having issues with the custom Item Renderer in your TileList not updating when the underlying data changes? Make sure you're not using an ArrayCollection, it doesn't quite work every time.

Pulling data from a .Net webservice and having to parse out the date from the xml with your own Date Utility? Just remember that in AS3 months, hours, minutes and seconds are 0 based... in other words, October is month 09, not 10. That took me a while to clue in to.

Want to display hierarchical data relationships in a tree view? Stick the child objects in an arraycollection and assign it to a property called children in the parent object. This can be repeated down the chain as often as you like but remember it can get a bit slow quickly. Then point the Tree dataProvider property at the topmost object of the hierarchy.

Sometimes building a Flex front end on a .Net webservice back end can get complicated, especially when the developer (me) doesn't have access to the webservice. If the .Net webservice is designed right, simply taking the resulting xml of the webservice calls and using HttpService calls to load the xml works. When it comes time to use the webservice directly, simply replace the HttpService calls with WebService calls. Works like a charm!

Here's something silly. I stuck aTabNavigator in my view, and tried to capture the click event on the tabs. I tried pretty much every event I could find but not a single one would trigger when clicking on the tab. I replaced the tabNavigator with a TabBar and a ViewStack, and then was able to trap the tabIndexChange event to get the desired result.

Not my usual blog style but things are pretty busy these days. I hope to blog more again in the new year.

Thursday, December 07, 2006

Autosizing Datagrid with Horizontal Scrolling

Today I was tasked with creating a spreadsheet like datagrid, one with more columns than what the available real estate would be able to show. The datagrid was to always use the maximum screen space available so that for those users with huge budgets and subsequently huge monitors would not need to scroll. For all other, more mortal users, the datagrid should simply scroll off to the right, with a nice horizontal scroll bar.

I figured this would be easy. Turns out it's a bit tricky. If I set the maxWidth of my datagrid to something smaller than the sum of my column widths, the desired behaviour would occur. I'd get a nice horizontal scroll bar and the columns would span out to the right. The problem with maxWidth is that it does not accept percentage values. So if I want the datagrid to use up 100% of the width of the parent container, I can't use maxWidth.

I tried sticking the datagrid in a canvas, thinking i'd be able to use the scrolling of the canvas to quasi hover/move over the datagrid rendered in its entirety underneath, but the vertical scrolling didn't really work out well. Perhaps someone else might have more luck with this approach. I kinda wanted to get back to using the scrollbars in the datagrid, as it seemed that this HAD to be possible!

I figured the solution had to lie in maxWidth. I put the datagrid inside a canvas and made the canvas size percentage based. I then, in a resize() function triggered by creationComplete of the app, assigned the value of the width of the canvas to the maxWidth property of my datagrid. This seemed to work. I also tried to have the resize event of the app call this function instead but I'd get null errors. Turned out I needed to assign the event listener on creationComplete.

Everything looked good, the datagrid was resizing with the canvas. At certain times however the datagrid seemed to lag behind and not fill the canvas properly. Using callLater fixed this. Here's the Demo. Right click on the demo to get the source.

Tuesday, December 05, 2006

Many-to-Many using FDS & Hibernate

Douglas McCarroll recently sent me an email asking if I'd had any luck getting Hibernate, Hibernate Tools for Eclipse and FDS to work with many to many relationships. I quickly threw together an example that I had built for a client a couple weeks ago, but in that particular case it wasn't a true many-to-many... I had a student, course and registration table but my registration table had it's own unique ID so really, it was just a combination of 2 one-to-many relationships. For most situations this would probably work however, as he pointed out, some clients might already have a db in place and would be unwilling to make a change just to accommodate the poor starving flex programmer.

Now back in the summer I did attempt to get a true many-to-many relationship to work using Hibernate Tools. What I didn't realize at the time is that the tools didn't yet support this feature. Then i saw this link and decided to give it another go.

I will be using mysql for this example. It is based loosely off the crm db that ships with FDS. Here's the scripts for the 3 tables in play:

CREATE TABLE  `crm`.`employee` (
`id` int(10) unsigned NOT NULL auto_increment,
`First_Name` varchar(45) NOT NULL default '',
`Last_Name` varchar(45) NOT NULL default '',
`Email` varchar(45) NOT NULL default '',
`Phone` varchar(45) NOT NULL default '',
`Title` varchar(45) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `crm`.`event` (
`id` int(10) unsigned NOT NULL auto_increment,
`name` varchar(200) NOT NULL default '',
`eventDate` datetime NOT NULL default '0000-00-00 00:00:00',
`eventTime` varchar(45) NOT NULL default '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

CREATE TABLE `crm`.`eventemployee` (
`eventID` int(10) unsigned NOT NULL default '0',
`employeeID` int(10) unsigned NOT NULL default '0',
PRIMARY KEY (`eventID`,`employeeID`),
KEY `FK_eventemployee_1` (`employeeID`),
CONSTRAINT `FK_eventemployee_1` FOREIGN KEY (`employeeID`) REFERENCES `employee` (`id`),
CONSTRAINT `FK_eventemployee_2` FOREIGN KEY (`eventID`) REFERENCES `event` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
As you can see I have 3 tables, an Employee table, an Event table and an EventEmployee table. The EventEmployee table only has 2 fields, each a foreign key to the corresponding table.

The next step was to ensure I had the right version of the Hibernate Tools installed in Eclipse (3.2.0.beta7 or greater). I'm not going to spend any time on explaining how to configure the Hibernate Tools.. needless to say I got the Tool configured to reverse-engineer my db and it created these .hbm.xml files:
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2006 1:55:21 PM by Hibernate Tools 3.2.0.beta8 -->
<hibernate-mapping>
<class name="samples.crm.Employee" table="employee" catalog="crm">
<comment></comment>
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<property name="firstName" type="string">
<column name="First_Name" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="lastName" type="string">
<column name="Last_Name" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="email" type="string">
<column name="Email" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="phone" type="string">
<column name="Phone" length="45" not-null="true">
<comment></comment>
</column>
</property>
<property name="title" type="string">
<column name="Title" length="45" not-null="true">
<comment></comment>
</column>
</property>
<set name="events" inverse="true" table="eventemployee">
<key>
<column name="employeeID" not-null="true">
<comment></comment>
</column>
</key>
<many-to-many entity-name="samples.crm.Event">
<column name="eventID" not-null="true">
<comment></comment>
</column>
</many-to-many>
</set>
</class>
</hibernate-mapping>

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd">
<!-- Generated Dec 5, 2006 1:55:21 PM by Hibernate Tools 3.2.0.beta8 -->
<hibernate-mapping>
<class name="samples.crm.Event" table="event" catalog="crm">
<comment></comment>
<id name="id" type="java.lang.Integer">
<column name="id" />
<generator class="native" />
</id>
<property name="name" type="string">
<column name="name" length="200" not-null="true">
<comment></comment>
</column>
</property>
<property name="eventDate" type="timestamp">
<column name="eventDate" length="19" not-null="true">
<comment></comment>
</column>
</property>
<property name="eventTime" type="string">
<column name="eventTime" length="45" not-null="true">
<comment></comment>
</column>
</property>
<set name="employees" inverse="true" table="eventemployee">
<key>
<column name="eventID" not-null="true">
<comment></comment>
</column>
</key>
<many-to-many entity-name="samples.crm.Employee">
<column name="employeeID" not-null="true">
<comment></comment>
</column>
</many-to-many>
</set>
</class>
</hibernate-mapping>
I also configured the Tools to create my domain code. What I found encouraging is that it only created the Employee.java and the Event.java classes, no EventEmployee.java class. Things were looking up! Ok, now it was time to configure the corresponding destinations in my data-management-config.xml file. I took a bit of an educated guess here, since the fds documentation surrounding the metadata tags is pretty weak... lucky for me I got it first try - I guess I spend way too much time here :)
<destination id="crm.employee.hibernate">
<adapter ref="java-dao" />
<properties>
<use-transactions>true</use-transactions>
<source>flex.data.assemblers.HibernateAssembler</source>
<scope>application</scope>
<metadata>
<identity property="id"/>
<many-to-many property="events"
destination="crm.event.hibernate" lazy="false" />
</metadata>
<network>
<session-timeout>20</session-timeout>
<paging enabled="false" pageSize="10" />
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="REPLACE" max-frequency="500"/>
</network>
<server>
<hibernate-entity>samples.crm.Employee</hibernate-entity>
<fill-configuration>
<use-query-cache>false</use-query-cache>
<allow-hql-queries>true</allow-hql-queries>
</fill-configuration>
</server>
</properties>
</destination>

<destination id="crm.event.hibernate">
<adapter ref="java-dao" />
<properties>
<use-transactions>true</use-transactions>
<source>flex.data.assemblers.HibernateAssembler</source>
<scope>application</scope>
<metadata>
<identity property="id"/>
<many-to-many property="employees"
destination="crm.employee.hibernate" lazy="false" />
</metadata>
<network>
<session-timeout>20</session-timeout>
<paging enabled="false" pageSize="10" />
<throttle-inbound policy="ERROR" max-frequency="500"/>
<throttle-outbound policy="REPLACE" max-frequency="500"/>
</network>
<server>
<hibernate-entity>samples.crm.Event</hibernate-entity>
<fill-configuration>
<use-query-cache>false</use-query-cache>
<allow-hql-queries>true</allow-hql-queries>
</fill-configuration>
</server>
</properties>
</destination>
Finally in my flex application I built my correponding .as classes:
package samples.crm
{
import mx.collections.ArrayCollection;
[Managed]
[RemoteClass(alias="samples.crm.Event")]
public class Event{
public function Event() {}
public var id:int;
public var name:String;
public var eventDate:Date;
public var eventTime:String;
public var employees:ArrayCollection;
}
}

package samples.crm
{
import mx.collections.ArrayCollection;
[Managed]
[RemoteClass(alias="samples.crm.Employee")]
public class Employee {
public function Employee() {}
public var id:int;
public var firstName:String;
public var lastName:String;
public var title:String;
public var phone:String;
public var email:String;
public var events:ArrayCollection;
}
}
Then I just created a quick application with 2 datagrids: one id="dg" with a dataProvider="{employees}" and the other id="childDg" with a dataProvider="{dg.selectedItem.events}" - by setting lazy to false on my many-to-many relationship in the destination, it's guaranteed that the child records will load automatically. You could also turn this around... create a datagrid that points to {events} and have the other point to {dg.selectedItem.employees}. By the time you get to this point you can do pretty much anything anyway. The real trick was getting here!