Exchanging User Defined Objects Between ColdFusion and Flex
As I've blogged about before I really appreciate how well Flex 2.0 works with ColdFusion MX 7 on the back end. One area I'm learning is how to exchange an object between Flex and CF. The goal being that both CF and Flex will be able to understand the object's class type and access the object's properties. Many of my CF components (for example a PersonDAO) have functions that either require an object of a specific type be sent as an argument and/or return an object of a specific type. If I want to use these same CFCs with Flex, I need Flex and CF to be able to exchange objects of a specific type.
My main reference for this tutorial is an old (in Internet time) blog entry by Ben Forta about automatic CFC - ActionScript conversion between CF and Flex 2.0 (at that time a beta version). I've developed an example of my methodology for exchanging objects between Flex and CF. My example's focus is on the exchange of the object, so it's not a full-scale Flex application.
Let's start with the CF part first. Below is the code for my Person CFC. This is a standard CFC with instance variables (what a Person knows) and mutator/accessor methods for setting and getting the values of the instance variables. I'm assuming you understand how to create CFCs. Note that I've used the cfproperty tag to document the CFC's instance fields (properties). Using the cfproperty tag is required if you want to relate the CFC with an ActionScript class in your Flex application.
<cfcomponent displayName="Person" hint="Class represents a Person">
<!---document each instance variable--->
<cfproperty name="personID" displayName="Person ID" hint="A unique Person ID is assigned to each record" type="numeric" default="0">
<cfproperty name="firstName" displayName="First Name" hint="Person's first name" type="string" default="">
<cfproperty name="lastName" displayName="Last Name" hint="Person's last name" type="string" default="">
<cfproperty name="phoneNumber" displayName="Phone Number" hint="Person's phone number ( format is (999) 999-9999 )" type="string" default="">
<!---Initialization area (similar to default constructor)--->
<cfscript>
variables.personID = 0;
variables.firstName = "";
variables.lastName = "" ;
variables.phoneNumber = "";
</cfscript>
<!---init method - can be used like overloaded constructor--->
<cffunction name="init" access="public" returntype="Person" output="no" hint="Set the initial values for the instance variables
and returns a Person object" >
<cfargument name="aPersonId" type="numeric" required="false" default="0" />
<cfargument name="aFirstName" type="string" required="false" default="" />
<cfargument name="aLastName" type="string" required="false" default="" />
<cfargument name="aPhoneNumber" type="string" required="false" default="" />
<cfscript>
setPersonID(arguments.aPersonID) ;
setFirstName(arguments.aFirstName);
setLastName(arguments.aLastName) ;
setPhoneNumber(arguments.aPhoneNumber);
</cfscript>
<cfreturn this />
</cffunction>
<!---mutator methods--->
<cffunction name="setPersonID" displayname="Set Person ID" access="public" returntype="void" output="no">
<cfargument name="aPersonID" type="numeric" required="true">
<cfset variables.personID = arguments.aPersonID>
</cffunction>
<cffunction name="setFirstName" displayname="Set First Name" access="public" returntype="void" output="no">
<cfargument name="aFirstName" type="string" required="true">
<cfset variables.firstName = arguments.aFirstName>
</cffunction>
<cffunction name="setLastName" displayname="Set Last Name" access="public" returntype="void" output="no">
<cfargument name="aLastName" type="string" required="true">
<cfset variables.lastName = arguments.aLastName>
</cffunction>
<cffunction name="setPhoneNumber" access="public" returntype="void" output="no">
<cfargument name="aPhoneNumber" type="string" required="true">
<cfset variables.phoneNumber = Arguments.aPhoneNumber>
</cffunction>
<!---accessor methods--->
<cffunction name="getPersonID" access="public" returntype="numeric" output="no">
<cfreturn variables.personID>
</cffunction>
<cffunction name="getFirstName" access="public" returntype="string" output="no">
<cfreturn variables.firstName>
</cffunction>
<cffunction name="getLastName" access="public" returntype="string" output="no">
<cfreturn variables.lastName>
</cffunction>
<cffunction name="getPhoneNumber" access="public" returntype="string" output="no">
<cfreturn variables.phoneNumber>
</cffunction>
</cfcomponent>
In addition to the Person CFC I have a PersonDAO (data access object) CFC. There are only two methods defined in this CFC (read and update) to demonstrate that feasibility of my example. The read method gets a specific record from the database table and updates a Person object's instance fields with the record's column values. The update method uses the state of a Person object to update a specific record in the database table. Note that both methods have remote access so that my Flex app may call these methods.
<cfcomponent displayname="PersonDAO" output="false" hint="Person DAO">
<!---document each instance variable--->
<cfproperty name="dsn" displayname="Datasource Name" hint="Datasource name used to connect CF to the database"
type="string">
<!---pseudo constructor--->
<cfscript>
variables.dsn = "BrucePresenter" ;
</cfscript>
<cffunction name="init" access="public" output="false" returntype="PersonDAO" hint="Constructor for this CFC">
<!--- take in the datasource name as an argument and set it to the variables scope so it's available
throughout the CFC --->
<cfargument name="dsn" type="string" required="true" />
<cfset variables.dsn = arguments.dsn />
<!--- return the object itself --->
<cfreturn this />
</cffunction>
<!--- READ: populates a Person object using info from the database --->
<cffunction name="read" access="remote" output="false" returntype="Person"
hint="Finds the record using personID of the person object passed in and then updates the person object's state
using the fields returned by the query. If no record is returned or more than one record is returned
throws a Person.NotFound exception ">
<cfargument name="aPerson" type="Person" required="true" />
<!--- var scope everything! --->
<cfset var getPerson = "" />
<cfquery name="getPerson" datasource="#variables.dsn#">
SELECT [presenterID] as personID, lastName, firstName, phone
FROM presenters
WHERE [presenterID] = <cfqueryparam value="#arguments.aPerson.getPersonId()#" cfsqltype="cf_sql_integer" />
</cfquery>
<cfif getPerson.recordcount neq 1>
<cfthrow type="Person.NotFound" message="I didnt find a person with personID: #arguments.aPerson.getPersonID()#">
</cfif>
<!--- if we got a record back, populate the object --->
<cfif getPerson.RecordCount EQ 1>
<!---update the state of the person object--->
<cfscript>
aPerson.setLastName( getPerson.lastName ) ;
aPerson.setFirstName( getPerson.firstName ) ;
aPerson.setPhoneNumber( getPerson.phone ) ;
</cfscript>
<cfreturn aPerson ><!---this is needed for Flex 2.0 since the Person oject passed to this method by the Flex app is apparently not passed by reference--->
</cfif>
</cffunction><!---end read method--->
<!--- UPDATE: updates an existing record in the database using the data in the object that's passed in --->
<cffunction name="update" access="remote" output="false" returntype="struct"
hint="Updates an existing record in the database and returns a struct containing a boolean and a string">
<cfargument name="aPerson" type="Person" required="true" />
<cfset var updatePerson = "" />
<cfset var results = StructNew() />
<cfset results.success = true />
<cfset results.message = "The person was updated successfully." />
<cftry>
<cftransaction action="begin"> <!---we need to insert data into two tables--->
<cfquery name="updatePerson" datasource="#variables.dsn#">
UPDATE Presenters
SET firstname = <cfqueryparam value="#arguments.aPerson.getFirstName()#" cfsqltype="cf_sql_varchar" />,
lastname = <cfqueryparam value="#arguments.aPerson.getLastName()#" cfsqltype="cf_sql_varchar" />,
phone = <cfqueryparam value="#arguments.aPerson.getPhoneNumber()#" cfsqltype="cf_sql_varchar" />
WHERE presenterID = <cfqueryparam value="#arguments.aPerson.getPersonId()#" cfsqltype="cf_sql_integer" />
</cfquery>
<!---update tblPeopleAddresses--->
<cftransaction action="commit" />
</cftransaction>
<cfcatch type="database">
<cfset results.success = false />
<cfset results.message = "A database error occurred: #CFCATCH.Detail#" />
<cftransaction action="rollback" />
</cfcatch>
</cftry>
<cfreturn results />
</cffunction><!--end update method--->
</cfcomponent>
Note that the read method returns an object of type Person. Normally my read method just updates the Person parameter object. Because of how objects are passed in CF, this technique also updates the Person object that was sent to the method. However, this methodology will not work for a Person object passed to the method from Flex. Whatever the read method does internally to the Person object parameter doesn't effect the Person object that is back in the Flex application. So my read method has to return back to the Flex application a Person object.
In my Flex application (right click on the Flex app to view the source) I have an ActionScript 3.0 class named Person also. This ActionScript class is the "twin" of my Person CFC. These lines of code in the ActionScript class:
[Bindable]
[RemoteClass(alias="brucephillips.flex.cfobject.Person")]
tell my Flex application that the CFC "twin" of this class is located on the path specified for the alias value. Also notice that each of my ActionScript Person's instance fields (properties) are declared public and the fields are listed in the same order and have the same type as declared in my Person CFC (as specified in the cfqueryparam tags). When I made the fields private, the exchange of the field values between Flex and CF did not work.
When the Flex application first loads, it creates a Person object using the ActionScript class and then updates the Person object with a hard-coded personID value (15844). The Flex app then passes this Person object to the CFC read method. The read method pulls from the database the record with the matching personID, updates the fields of the Person object, and returns the Person object back.
The Flex app receives the Person object back (see method handleReadResult). Since the form fields are bound to the ActionScript's Person object the form fields are populated with the values from the Person object. The Update button is enabled and the user can now change the values in the form fields.
When the user clicks on the Update button, the ActionScript Person object's fields are updated with the form field values (note I don't do any data validation - NOT A GOOD PROCEDURE). Then the Person object is passed to the update CFC method. The update CFC method uses the values of the Person object's instance fields to update the table columns for that specific record. The CFC update method returns a structure with a message and Flex places the message's value into the updateMsg String variable (see method handleUpdateResult).
This example demonstrates how we can exchange objects of user defined data types (in this case class Person) between Flex and CF. Existing CFC that are used to create specific types of objects and/or require specific types of objects as arguments and/or return specific types of objects can be leveraged in your Flex applications by applying this methodology.
Your post saved my sanity! :)
trace(ObjectUtil.toString(event.result));
within the handler, I see all of the user info (first name, last name, etc) that I expect, but when do
loggedInUser = event.result as myUserObject;
loggedInUser (of type myUserObject) comes back null.
Any idea what I may be doing wrong?
ps. Thanks for all the effort you put into your site, it has been invaluable as I battle my way through Flex!
For instance see: http://tinyurl.com/23k8cz