10 May 2010
This tutorial is intended for advanced developers who have knowledge of CFML, MXML, Flex 4, and ActionScript 3.0, as well as experience with ColdFusion 9, MySQL, ColdFusion Administrator, ORM concepts, and object-oriented programming.
Advanced
In this tutorial you will learn how to integrate a Flex 4 application and ColdFusion 9, using Object Relational Mapping (ORM) for communication with the database.
Flex, ActionScript, and MXML allow you to easily build applications for Flash Player that communicate with a wide variety of servers, using services such as BlazeDS or LiveCycle Data Services for data source access. This capability is helping large companies replace legacy desktop software with Rich Internet Applications (RIAs).
ColdFusion 9 is a dynamic, practical back end for RIAs. Perhaps the biggest breakthrough in this last release of ColdFusion is the inclusion of Hibernate to give ColdFusion native ORM support. With a Flash Remoting engine for communicating with Flex applications, ColdFusion 9 is poised to be the perfect back end for your RIAs.
The development scenario for this article is based building an application for a client who provided the following requirements:
By combining the capabilities of Flash Builder and ColdFusion Builder, you can set up a development environment for efficient RIA development. You can install ColdFusion Builder as a plug-in to Flash Builder, or you can install Flash Builder as a plug-in to ColdFusion Builder. Either option will work because both products are based on Eclipse, and they inherit Eclipse's powerful plug-in capabilities.
To get started you'll need to install ColdFusion 9 (use the server configuration), Flash Builder 4, and ColdFusion Builder. The instructions that follow assume you have installed ColdFusion Builder as a plug-in to Flash Builder; you may need to modify them slightly if you have installed Flash Builder as a plug-in to ColdFusion Builder or both products as plug-ins to another Eclipse deployment.
Note: The instructions that follow can be used to set up a project that uses Flex and ColdFusion, such as the example project. You can also set up the project by importing the sample files, as explained in Importing the source code.
Follow these steps to set up the project in Flash Builder and add Flex properties:
As a practice, I create a folder below the ColdFusion server root, and immediately below that I create a subfolder for the company that the project is for. Below that I can store multiple projects for the company. In this case I specified C:\ColdFusion9\wwwroot\com\pcsilva\adc\lesson1 as the location.
At this point, your ColdFusion project is configured, and you need to add Flex properties to it.
Next, type your ColdFusion root directory and web root, and then click Validate (see Figure 6). (I also changed the Output Folder to html; this is where the SWF is stored after compilation.)
Now you'll need to add to the project two SWC files, fds.swc (for Flex Data Services) and cfservices.swc (for ColdFusion services).
Next you'll need to configure the data source for the project.
You can use the sample files in cf9_orm_flex4.zip to set up the project and you can use them as support material; follow these steps to import the sample source code:
You can use the ColdFusion Builder Extension Adobe CFC Generator to accelerate deployment by automatically creating ORM entities, their respective Data Access Objects (DAOs), and the needed directory structures. Follow these steps:
In the Create ORM CFC dialog box (see Figure 16), the steps are more complicated this time.
Now, you have created two entities and two DAOs.
cfproperty named group.fetch="join" , which will make the User object use queries with JOIN in them to load data into the related Group object and reduce the number of database queries you'll need to make to populate the User object.remotingfetch="true" , to ensure that it executes a fetch for remoting calls. The code should now read as follows: <cfproperty name="group"
remotingfetch="true" fetch="join"
fieldtype="many-to-one" cfc="Group"
fkcolumn="group_id" >
Next you need to create a CFC that will simplify access to the application server DAO from Flex. This CFC provides data model access, allowing the Flex application to work as a controller, and serves as a single source for the endpoint. In the sample files, you'll find UserFacade.cfc, which includes both entities in just one file.
component output="false" {
import com.pcsilva.adc.article1.cfml.entity.User;
import com.pcsilva.adc.article1.cfml.entity.Group;
import com.pcsilva.adc.article1.cfml.dao.UserDAO;
import com.pcsilva.adc.article1.cfml.dao.GroupDAO;
userDAO = new UserDAO();
groupDAO = new GroupDAO();
/*
* User opertators
*/
remote User function saveUser(User user)
{
return userDAO.Save(user);
}
remote void function deleteUser(numeric ID)
{
return userDAO.DeleteUser(ID);
}
remote Array function getUsers()
{
return userDAO.GetAllUser();
}
remote Array function getUsers_paged(numeric startIndex, numeric numItems)
{
return userDAO.GetUsers_paged(startIndex,numItems);
}
remote User function getUser(numeric ID)
{
return userDAO.GetUser(ID);
}
remote numeric function countUser()
{
return userDAO.Count();
}
//specific method for client
remote Array function getUsersByGroup(Group group)
{
//return userDAO.GetUsersByGroup(group);
//this must be done in userDAO.GetUsersByGroup
var foreignKeysMap = { group = group };
return entityload("User",foreignKeysMap);
}
/*
* Group opertators
*/
remote Group function saveGroup(Group group)
{
return groupDAO.save(group);
}
remote void function deleteGroup(numeric ID)
{
return groupDAO.DeleteGroup(ID);
}
remote Array function getGroups()
{
return groupDAO.GetAllGroup();
}
remote Array function getGroups_paged(numeric startIndex, numeric numItems)
{
return groupDAO.GetGroups_paged(startIndex,numItems);
}
remote Group function getGroup(numeric ID)
{
return groupDAO.GetGroup(ID);
}
remote numeric function countGroup()
{
return groupDAO.Count();
}
}
To complete the ColdFusion configuration, you should verify the destination named ColdFusion in your C:/ColdFusion9/wwwroot/WEB-INF/flex/remoting-config.xml file.
If it does not exist, add the destination shown in bold below. When you set <source>*</source> within a destination , ColdFusion Remoting enables all CFC methods with access="REMOTE" to be accessed by the Flex application.
<?xml version="1.0" encoding="UTF-8"?>
<service id="remoting-service"
class="flex.messaging.services.RemotingService"
messageTypes="flex.messaging.messages.RemotingMessage">
<adapters>
<adapter-definition id="cf-object" class="coldfusion.flash.messaging.ColdFusionAdapter" default="true"/>
<adapter-definition id="java-object" class="flex.messaging.services.remoting.adapters.JavaAdapter"/>
</adapters>
<default-channels>
<channel ref="my-cfamf"/>
</default-channels>
<destination id="ColdFusion">
<channels>
<channel ref="my-cfamf"/>
</channels>
<properties>
<source>*</source>
</properties>
</destination>
</service>
With the UserFacade service implemented and managing access to the entities you can now turn to the development of the Flex application.
Entities that are on the server must be mirrored in ActionScript. This enables the reflection of the database tables in the application view, maintains data integrity, and makes client side validation possible.
You can generate the ActionScript Value Objects (VOs) using another ColdFusion Builder Extension:
Take a moment to review the generated ActionScript VO and verify that it reflects the ColdFusion VO. You can also change it to meet customer needs.
package com.pcsilva.adc.article1.mxml.entity
{
[Bindable]
// pointing to an entity in ColdFusion
[RemoteClass(alias='com.pcsilva.adc.article1.cfml.entity.User')]
// declare entity
[Entity]
public class User extends Base
{
private var _id:Number = 0; // NaN is not going to ColdFusion
public var name:String;
public var lastname:String;
public var email:String;
// statement of relationship, pointing to an entity in ColdFusion
[ManyToOne(targetEntity="com.pcsilva.adc.article1.cfml.entity.Group", fetchType='EAGER', cascadeType='All')]
[JoinColumn(name='group_id',referencedColumnName='id')]
// start property for use in getter groupname
public var group:Group = new Group();
[Id]
//statement identifier
public function get id():Number
{
return _id;
}
public function set id(value:Number):void
{
if(_id != value)
{
selectedIndex++;
}
_id = value;
}
[Transient] // which is not reflected in ColdFusion
// add property read-only – user.fullname
public function get fullname():String
{
return name+' '+lastname;
}
[Transient]
// user.groupname simplifies access the property user.group.name
public function get groupname():String
{
return group.name;
}
}
}
At this point, you need to create UserService.as, which is a simplified implementation of the Model Locator pattern containing properties that reflect the client's needs (see code below). It includes variables representing the current User, two ArrayCollections containing lists of Users and Groups, a general method for fault communication, and methods to enable transactions through the UserFacade. The other constants (const) are to standardize the text.
package com.pcsilva.adc.article1.mxml.services
{
import com.pcsilva.adc.article1.mxml.entity.Group;
import com.pcsilva.adc.article1.mxml.entity.User;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.remoting.Operation;
import mx.rpc.remoting.RemoteObject;
public class UserService
{
private var _remoteObject:RemoteObject;
/* it perfoms ownership lock transaction */
private var _inUse:Boolean = false;
private static var _instance:UserService;
/* constant configuration remoting */
public static const DESTINATION:String = "ColdFusion";
public static const SOURCE:String = "com.pcsilva.adc.article1.cfml.facade.UserFacade";
public static const SHOWBUSYCURSOR:Boolean = true;
/* constants with names of methods for transactions */
public static const OPERATION_LIST_GROUP:String = 'getGroups';
public static const OPERATION_LIST_USER:String = 'getUsersByGroup';
public static const OPERATION_SAVE_USER:String = 'saveUser';
/* constant system, property names in VO */
public static const LABELFIELD_GROUP:String = 'name';
public static const LABELFIELD_USER:String = 'fullname';
/* constants with texts for the VIEW */
public static const TITLE:String = 'ColdFusion ORM Flex';
public static const LIST_GROUPS:String = 'Groups';
public static const LIST_USERS:String = 'Users';
public static const LABEL_USER:String = 'User';
public static const LABEL_USER_NAME:String = 'Name';
public static const LABEL_USER_LASTNAME:String = 'Last name';
public static const LABEL_USER_EMAIL:String = 'E-mail';
public static const LABEL_GROUP:String = 'Group';
public static const MESSAGE_ERROR_TITLE:String = 'Error';
public static const MESSAGE_ERROR_CONTENT:String = 'Invalid user!';
/* singleton data for VIEW */
[Bindable] public var user:User;
[Bindable] public var group:Group;
[Bindable] public var users:ArrayCollection;
[Bindable] public var groups:ArrayCollection;
/* system methods */
public function clean(instance:SingletonUserService=null):void
{
users = new ArrayCollection();
user = new User();
if(instance != null)
{
groups = new ArrayCollection();
}
}
protected function onFault(evt:FaultEvent):void
{
_inUse = false;
Alert.show(evt.fault.message);
}
/* start constructor */
public function UserService(instance:SingletonUserService)
{
if(instance != null)
{
clean(instance);
_remoteObject = new RemoteObject();
_remoteObject.destination = DESTINATION;
_remoteObject.source = SOURCE;
_remoteObject.showBusyCursor = SHOWBUSYCURSOR;
_remoteObject.addEventListener(FaultEvent.FAULT, onFault);
}
}
public static function getInstance():UserService
{
if(_instance == null)
{
_instance = new UserService(new SingletonUserService());
}
return _instance;
}
/* end constructor */
/* start getGroups */
public function getGroups(result:Function=null):void{
clean(new SingletonUserService());
/* I capture operation in UserFacade, without dirtying the base configuration of remoting in Flex */
var service:Operation = Operation(_remoteObject.getOperation(OPERATION_LIST_GROUP));
service.addEventListener(ResultEvent.RESULT,onGetGroups);
if(result != null)
{
service.addEventListener(ResultEvent.RESULT,result);
}
service.send(null);
}
protected function onGetGroups(evt:ResultEvent):void {
[ArrayElementType('com.pcsilva.adc.article1.mxml.entity.Group')]
var result:Array = evt.result as Array;
groups.source = result;
}
/* end getGroups */
/* start getUsersByGroup */
public function getUsersByGroup(_group:Group,result:Function=null):void{
clean();
var service:Operation = Operation(_remoteObject.getOperation(OPERATION_LIST_USER));
service.addEventListener(ResultEvent.RESULT,onUsersByGroup);
if(result != null)
{
service.addEventListener(ResultEvent.RESULT,result);
}
service.send(_group);
}
protected function onUsersByGroup(evt:ResultEvent):void {
[ArrayElementType('com.pcsilva.adc.article1.mxml.entity.User')]
var result:Array = evt.result as Array;
users.source = result;
}
/* end getUsersByGroup */
/* start saveUser */
public function saveUser(result:Function=null,fault:Function=null):void{
/* it perfoms ownership lock transaction */
if(!_inUse){
_inUse = true;
var _user:User = user;
var service:Operation = Operation(_remoteObject.getOperation(OPERATION_SAVE_USER));
service.addEventListener(ResultEvent.RESULT,onSaveUser);
if(result != null)
{
service.addEventListener(ResultEvent.RESULT,result);
}
if(fault != null)
{
service.addEventListener(FaultEvent.FAULT, fault);
}
service.send(_user);
user = new User();
}
}
protected function onSaveUser(evt:ResultEvent):void {
_inUse = false;
}
/* end saveUser */
}
}
class SingletonUserService {}
Because the singleton distributes the service properties through all application components, you must implement the validators in the View.
The article is not focused on a refined user interface, so the example Flex application is fairly simple (see Figure 18). Instead the focus has been on data access via ColdFusion ORM, requests made to UserService, and how to handle the data returned by UserFacade.
If you open the Master.mxml file, you can see how it handles the data and how transparent the data views are. The component's creationComplete event starts the data retrieval process.
<?xml version="1.0" encoding="utf-8"?>
<mx:Form xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx" width="100%"
creationComplete="{userService.getGroups()}">
<fx:Script>
<![CDATA[
import com.pcsilva.adc.article1.mxml.entity.Group;
import com.pcsilva.adc.article1.mxml.entity.User;
import com.pcsilva.adc.article1.mxml.services.UserService;
import spark.events.IndexChangeEvent;
[Bindable]
private var userService:UserService = UserService.getInstance();
/* start user events */
// action is triggered when a group is selected
private function groupsList_changeHandler(event:IndexChangeEvent):void
{
if(event.currentTarget.selectedItem is Group){
userService.getUsersByGroup(event.currentTarget.selectedItem);
}
}
// ction is triggered when a user is selected
private function usersList_changeHandler(event:IndexChangeEvent):void
{
userService.user = new User();
if(event.currentTarget.selectedItem is User)
{
Group(User(event.currentTarget.selectedItem).group).selectedIndex = groupsList.selectedIndex;
userService.user = User(event.currentTarget.selectedItem);
}
}
/* end user events */
]]>
</fx:Script>
<mx:FormItem width="100%" label="{UserService.LIST_GROUPS}">
<s:ComboBox id="groupsList" change="{groupsList_changeHandler(event)}"
labelField="{UserService.LABELFIELD_GROUP}"
selectedIndex="{userService.group.selectedIndex >= 0?userService.group.selectedIndex:-1}"
dataProvider="{userService.groups}" width="100%" />
</mx:FormItem>
<mx:FormItem width="100%" label="{UserService.LIST_USERS}">
<s:List id="usersList"
change="usersList_changeHandler(event)"
labelField="{UserService.LABELFIELD_USER}"
dataProvider="{userService.users}" width="100%" height="100%" />
</mx:FormItem>
</mx:Form>
The sample code for this article was written in a simple style so that it can be understood and modified easily. You can do a lot more with the code than what has been covered here. Feel free to use this code as a starting place for your own efforts.

This work is licensed under a Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License
Tutorials & Samples |