Flash Remoting, the technology that allows Flash-based documents to communicate with Web-based resources over a high-speed, binary protocol, was first introduced with ColdFusion MX (also known as ColdFusion version 6). In the early days of the technology, before the introduction of Flex, applications built in Flash MX and subsequent releases had the ability to make remote procedure calls to functions of ColdFusion components (CFCs) over a standard Web connection.
When Flex 1.0 was released, Flash Remoting was adapted for use with Javabased application servers that hosted Java-based classes. Flex client applications could make calls to Java-based methods just as easily as with ColdFusion using the feature first known as Remote Object Services, now known as the Remoting Service.
The Java-based Remoting Service is described in Chapter 24.
Adobe ColdFusion 8 continues to offer built-in support for Flash Remoting with Flex-based and Flash-based client applications, and it adds the ability to integrate tightly with features that are unique to LiveCycle Data Services or BlazeDS. When ColdFusion is integrated with these Adobe products, you can build and deploy Flex applications that share messages in real time or near real time with ColdFusion-based resources using the Message Service, and you can use LiveCycle Data Services' Data Management Service to create applications that synchronize data between multiple connected clients and servers.
All the currently available ColdFusion features that support integration with Flex client applications were first introduced in ColdFusion version 7.02. With the exception of the event gateway used to integrate Flex applications with the Message Service, all the features described in this chapter work equally well with ColdFusion 7.02 or ColdFusion 8.
In this chapter, I describe how to use Flash Remoting to call ColdFusion component functions from Flex applications. In Chapter 27, I describe how to use the ColdFusion Extensions for Eclipse to interact with ColdFusion during development and generate server-side ColdFusion components for use with Flex/ColdFusion applications.
To use the sample code for this chapter, download chapter26.zip
from the Web site. Follow the instructions later in this chapter to create a Flex project for use with ColdFusion and install the various project components to the client and server.
The feature known as the Remoting Service in LiveCycle Data Services and BlazeDS is known as Flash Remoting in ColdFusion. Flash Remoting allows you to directly make calls to functions of ColdFusion components using the Flex framework's RemoteObject
component.
Calls from a Flex client application are sent directly to the ColdFusion server as HTTP requests encrypted in Action Message Format (AMF), and responses are returned from ColdFusion to the Flex application without any intermediate proxy or additional software. As shown in Figure 26.1, when using the RemoteObject
component to call CFC methods, you don't need to install or integrate LiveCycle Data Services or BlazeDS.
Figure 26.1. Flash Remoting requests and responses travel directly from the Flex application to ColdFusion and back again.
To call CFC functions from a Flex application, follow these steps:
Install Adobe ColdFusion or obtain access to a ColdFusion server. You can use either the Standard or Enterprise edition or, if you don't have a license, you can install ColdFusion locally as the Developer edition. This edition is free for development and testing and has some limitations, including allowing connections from only two browser clients during any particular session.
If using Flex Builder, optionally install the ColdFusion Extensions for Flex Builder.
Create ColdFusion components on the server with code you can call from a Flex application.
If using Flex Builder, create a Flex project that's integrated with your ColdFusion server installation.
Create and test Flex client code to call the CFC functions.
The sample code and instructions in this chapter assume that ColdFusion 8 has been installed on a Windows-based development system using the development Web server running on port 8500. If your ColdFusion installation differs, adapt the instructions as needed.
When you create a new Flex project, you can add project properties that allow you to easily test your Flex application with the ColdFusion server. Follow these steps to create a new Flex project:
Select File → New → Flex Project from the Flex Builder menu.
On the first screen, set these properties, as shown in Figure 26.2:
Project name: chapter26
Use default location: selected
Application type: Web application
Application server type: ColdFusion
Use remote object access service: selected
ColdFusion Flash Remoting: selected
Click Next.
On the Configure ColdFusion Server screen, set the ColdFusion installation type and location. If you installed the server configuration to the default location in Windows, use these settings:
ColdFusion installation type: Standalone
Use default location for local ColdFusion server: selected
Use built-in ColdFusion web server: selected
Click Validate Configuration to verify that your ColdFusion configuration settings are accurate.
ColdFusion must be running in order to validate the configuration at this point. Flex Builder sends a test request to the ColdFusion server's Root URL to ensure that the server is reachable.
Set the Output folder to a location under the ColdFusion Root URL. This is the folder where the application's debug output files will be generated during the compilation process and from which you retrieve documents in the browser during testing. The default setting is a subfolder under the ColdFusion Web root whose name starts with the project name and ends with "-debug".
Click Next.
Accept the Main application filename of chapter26
.mxml.
Click Finish to create the project and application.
Run the application.
You should see that the application is retrieved from the folder on the ColdFusion server using the server's root URL and the subfolder in which the output files are generated.
If you want to use the sample applications for this chapter from the Web site, follow these instructions: Extract the contents of chapter26.zip
to the new project's root folder, locate chapter26CFFiles.zip
in the project files, and extract those files to the ColdFusion server's Web root folder, such as C:ColdFusion8wwwroot
. This creates a new subfolder named flex3bible/chapter26
under the ColdFusion Web root.
Just as with the Remoting Service on LiveCycle Data Services or BlazeDS, to use Flash Remoting from a Flex application, it must be configured on the server. When you install ColdFusion, a folder named WEB-INF/flex
is created that contains a default set of configuration files.
The WEB-INF
folder is located under the ColdFusion Web root, as defined during the ColdFusion installation process. The actual location differs depending on ColdFusion's configuration.
The "server" configuration that includes a limited JRun server places WEB-INF
under the installation folder's wwwroot
subfolder. When the server configuration of ColdFusion is installed on Windows with the default location, the Flex configuration files are stored in C:ColdFusion8wwwrootWEB-INFflex
. For the J2EE or multi-server configurations, WEB-INF
is located under the ColdFusion "context root" folder. For example, when installed with the default location on Windows, the Flex configuration files are stored in C:JRun4serverscfusioncfusion-earcfusion-warWEB-INFflex
.
Flash Remoting access is configured in one of two files in the WEB-INF/flex
folder:
If you install ColdFusion with the integrated LiveCycle Data Services, the primary services configuration file, services-config
.xml, includes a file named remotingconfig
.xml that declares Flash Remoting configuration options.
If you install ColdFusion without the integrated LiveCycle Data Services, a file named service-config
.xml contains all Flex server configuration options, including Flash Remoting destination definitions.
ColdFusion includes a predefined Flash Remoting destination definition with an id
of ColdFusion
in either services-config
.xml or remoting-config
.xml that looks like this:
<destination id="ColdFusion"> <channels> <channel ref="my-cfamf"/> </channels> <properties> <source>*</source> <!-- define the resolution rules and access level of the cfc being invoked --> <access> <!-- Use the ColdFusion mappings to find CFCs, by default
only CFC files under your webroot can be found. --> <use-mappings>false</use-mappings> <!-- allow "public and remote" or just "remote" methods be invoked --> <method-access-level>remote</method-access-level> </access> <property-case> <!-- cfc property names --> <force-cfc-lowercase>false</force-cfc-lowercase> <!-- Query column names --> <force-query-lowercase>false</force-query-lowercase> <!-- struct keys --> <force-struct-lowercase>false</force-struct-lowercase> </property-case> </properties> </destination>
These key destination properties are worth a detailed description:
The <channel>
is set to my-cfamf. This channel is defined in services.config
.xml, and uses the AMFChannel
class on the server to serialize and deserialize AMF-formatted messages between client and server. The services configuration file also defines a channel named my-cfamf-secure
that can be used for encrypted communications over SSL.
The <source>
element is set to a wildcard value of *, meaning that the predefined ColdFusion
destination can be used to call any CFC on the server. This is in contrast to the use of the Java-based Remoting Service with LCDS and BlazeDS, where each Java class must be configured with its own unique destination.
The <access>
element determines which functions can be called from the Flex application. The default setting of remote
means that only functions whose access attribute is set to remote can be called from Flex.
The settings in the <property-case>
element determine whether property names are forced to lowercase as they're returned from ColdFusion to the Flex client application. Because ColdFusion is mostly case-insensitive, the names of CFC properties, structure properties, and query columns are automatically returned in uppercase. The code works fine, but it looks odd to the eye of a developer who's accustomed to object-oriented coding conventions, where property names always start with initial lowercase characters. If you want to change the default behavior, switch the value of the case properties to true to force lowercase names:
<property-case> <force-cfc-lowercase>true</force-cfc-lowercase> <force-query-lowercase>true</force-query-lowercase> <force-struct-lowercase>true</force-struct-lowercase> </property-case>
The default ColdFusion
destination is designed to be usable with its initial property settings. In most cases, you can start calling CFC functions from a Flex client application without changing any of the server-side configuration options.
The rules for creating ColdFusion components for use with a Flex application are very similar to those CFCs used as SOAP-based Web services:
CFCs should be placed in a folder under the Web root. With additional configuration, you also can place CFCs in folders that are mapped through the ColdFusion administrator.
CFC functions should have their access
attribute set to remote
. With additional configuration, you also can expose functions with their access
set to public
.
Functions should return values with data types that are compatible with Flex applications.
A complete description of how to create and deploy ColdFusion components is beyond the scope of this chapter. For a good starting tutorial on this subject, see Ben Forta's article on the Adobe Developer Center Web site at:
www.adobe.com/devnet/coldfusion/articles/intro_cfcs.html
Table 26.1 describes the data types that can be returned from a CFC to a Flex application and how each value is translated into ActionScript variables when it's returned from a CFC function to a Flex client application.
Table 26.1. Data Conversion from ColdFusion to ActionScript
ColdFusion Data Type | ActionScript Data Type |
---|---|
|
|
|
|
|
|
|
|
CFC instance | Strongly typed value object |
|
|
|
|
XML Object | XML Object |
The returned data type is determined in a ColdFusion function by its returntype
property. The CFC in Listing 26.1 shows a ColdFusion component with a helloWorld()
function that declares a returntype
of string
:
Example 26.1. A simple ColdFusion component
<cfcomponent name="HelloService" output="false" hint="A ColdFusion Component for use in Flash Remoting"> <cffunction name="helloWorld" returntype="string" access="remote"> <cfreturn "Hello from a ColdFusion Component!"/> </cffunction> </cfcomponent>
The code in Listing 26.1 is available in the Web site files as HelloService.cfc
in the ColdFusion files of the chapter26
project.
Unlike the worlds of Java and ActionScript, ColdFusion Markup Language is mostly case-insensitive. As a result, returntype
values of string
and String
mean the same thing.
The returntype
attribute is also used by ColdFusion to verify that data being returned by a function is of the correct type. CFML (ColdFusion Markup Language) is a very loosely typed language, where simple values are generally stored as String values until being cast appropriately at runtime (a process sometimes known as "lazy evaluation"). But at runtime, ColdFusion can detect discrepancies between a declared returntype
and the actual value being returned. This function, for example, would generate a server-side runtime error, because the value being returned can't be parsed as a number:
<cffunction name="getNumber" returntype="numeric" access="remote"> <cfreturn "This is not a numeric value"/> </cffunction>
This resulting server-side error would be exposed in the Flex client application as a fault
event dispatched by the RemoteObject
that made the remote call to the function.
To call a CFC function from a Flex client application, you start by creating an instance of the RemoteObject
component. This is the same RPC component that's used to communicate with ColdFusion is almost exactly the same. If the source of the component is set to a wildcard in the server-side destination, you set the component's source property in the client-side RemoteObject
declaration.
The CFC is known to the Flex client application by its fully qualified name and location, declared with dot notation. This String
value is passed to the RemoteObject
component's source property to determine which component will be called on the server.
ColdFusion uses a naming pattern whereby CFCs are known by the name of the file in which the component is defined (without the file extension), prefixed with the names of the folders in which it's stored, starting at the Web root folder. Folder and component names are separated with dot characters, just like packages in Java. So, for example, a CFC that's defined in a file named MyComponent.cfc
and stored in a subfolder under the ColdFusion Web root named flex3bible/cfc
would be referred to from Flex as:
flex3bible.cfc.MyComponent
If you're working on a development server that has RDS, you can generate a CFC's documentation by navigating to the component from a Web browser. The documentation includes the exact string you need to set the component's source accurately in Flex. For example, you can browse to the HelloService
.cfc file stored in the Web root folder's flex3bible/chapter26
folder with this URL:
http://localhost:8500/flex3bible/chapter26/HelloService.cfc
If you have RDS security turned on in the ColdFusion administrator, you'll need to enter your RDS password to view the CFC's documentation.
Figure 26.3 shows the resulting CFC documentation. The string value you use as the source attribute in Flex is displayed twice: once at the top of the documentation page and again in the hierarchy section.
You can create an instance of the RemoteObject
component that works with the ColdFusion
destination in either MXML or ActionScript. In addition to the object's unique id
, you set object's destination
to the id of the destination on the server, named by default ColdFusion
. The object's source
attribute is set to the fully qualified name and location of the CFC, as described in the preceding section.
The code to create a RemoteObject
and set its required properties in MXML looks like this:
<mx:RemoteObject id="helloService" destination="ColdFusion" source="flex3bible.chapter26.HelloService"/>
Figure 26.3. Automatically generated CFC documentation, including the component's fully qualified name and location
The code to create the same object in ActionScript looks like this:
import mx.rpc.remoting.RemoteObject; var helloService:RemoteObject = new RemoteObject("ColdFusion"); helloService.source = "flex3bible.chapter26.HelloService";
After you've declared the RemoteObject
and set its source and destination
properties, you're ready to make runtime calls to remote CFC functions.
You call CFC functions as though they were local methods of the RemoteObject
. For example, the CFC in Listing 26.1 has a public method named helloWorld()
that returns a simple String
. As with local functions, you can call the remote method upon any application event. For example, this code calls the server-side helloWorld()
method upon a Button component's click
event:
<mx:Button label="Click to say hello" click="helloService.helloWorld()"/>
You also can call a CFC function by calling the RemoteObject
component's getOperation()
method to create an instance of the Operation
class. The following code creates the Operation
object and then calls its send()
method to call the remote method:
import mx.rpc.remoting.mxml.Operation; private function callIt():void { var op:Operation = helloService.getOperation("helloWorld") as Operation; op.send(); }
This technique allows you to determine which remote method will be called at runtime, instead of having to hard code the method name.
Calls to remote CFC functions are made asynchronously in Flex, so a call to a CFC function doesn't return data directly. Instead, as with the other RPC components, you handle the response with binding expressions or event handlers. Binding expressions require less code and are easy to create, while event handlers offer much more power and flexibility in how you receive, process, and save data to application memory.
A binding expression used to pass returned data to application components consists of three parts, separated with dots:
The RemoteObject
instance's id
The CFC function name
The lastResult
property
At runtime, the method is created as an Operation
object that's a member of the RemoteObject
instance. The Operation
object's lastResult
property is populated with data when it's received from the server.
The lastResult
property is explicitly typed as an ActionScript Object, but at runtime its native type is determined by the type of data that's returned from the server. A String
returned from ColdFusion is translated into an ActionScript String value, so a binding expression that handles the value returned from the simple helloWorld()
method can be used to pass the returned value to a Label
or other text display control.
The application in Listing 26.2 calls the remote helloWorld()
CFC function and displays its returned data in a Label
control with a binding expression in its text
property.
Example 26.2. Handling returned data with a binding expression
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE"> <mx:RemoteObject id="helloService" destination="ColdFusion" source="flex3bible.chapter26.HelloService"/> <mx:Button label="Hello World" click="helloService.helloWorld()"/> <mx:Label text="{helloService.helloWorld.lastResult}" fontSize="12"/> </mx:Application>
The code in Listing 26.2 is available in the Web site files as ROWithBinding.mxml
in the src
folder of the chapter26
project.
As with other RPC components, you can handle results of a call to a CFC function with the RemoteObject
component's result
event. This event dispatches an event object typed as mx.rpc.events.ResultEvent
, the same event object that's used by the other RPC components HTTPService
and RemoteObject
. The event object's result
property references the returned data.
To handle and save data using the result
event, follow these steps:
Declare a bindable variable outside of any functions that acts as a persistent reference to the returned data. Cast the variable's type depending on what you expect to be returned by the remote method. For example, if the data returned by the CFC function is typed as a Query, the RemoteObject
component casts the returned data as an ArrayCollection
. This code declares a bindable ArrayCollection
variable:
import mx.collections.ArrayCollection; [Bindable] private var myData:ArrayCollection
Create an event handler function that will be called when the event is dispatched. The function should receive a single event
argument typed as ResultEvent
and return void:
private function resultHandler(event:ResultEvent):void { }
Within the event handler function, use the event.result
expression to refer to the data that's returned from the server. Just as with the WebService
component, ResultEvent.result
is typed as an Object
. Because the expression's native type differs depending on what's returned by the CFC function, you typically have to explicitly cast the returned data. This code expects the CFC function to return an ArrayCollection
:
myData = event.result as ArrayCollection;
You can listen for the result event with either an MXML attribute-based event listener or a call to the ActionScript addEventListener()
method. The attribute-based event listener looks like this:
<mx:RemoteObject id="myService" destination="helloClass" source="flex3bible.chapter26.ContactService" result="resultHandler(event)"/>
When using addEventListener()
to create an event listener, you can designate the event name with the String
value result, or with the ResultEvent
class's RESULT static constant:
var contactService:RemoteObject = new RemoteObject("ColdFusion"); contactService.source = source="flex3bible.chapter26.ContactService"; contactService.addEventListener(ResultEvent.RESULT, resultHandler); contactService.getAllContacts();
Listing 26.3 uses a result
event handler function to capture and present data that's been returned from a CFC function.
Example 26.3. Handling results from a CFC function with a result event handler
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE" creationComplete="initApp()" xmlns:view="view.*"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import mx.rpc.remoting.RemoteObject; [Bindable] private var contactData:ArrayCollection; private var contactService:RemoteObject; private function initApp():void {
contactService = new RemoteObject("ColdFusion"); contactService.source = "flex3bible.chapter26.ContactService"; contactService.addEventListener(ResultEvent.RESULT, resultHandler); } private function resultHandler(event:ResultEvent):void { contactData = event.result as ArrayCollection; } ]]> </mx:Script> <mx:Button label="Hello World" click="contactService.getAllContacts()"/> <view:ContactsGrid dataProvider="{contactData}"/> </mx:Application>
The code in Listing 26.3 is available in the Web site files as ROResultHandler.mxml
in the src
folder of the chapter26
project. The custom DataGrid
component that displays data in this and other applications in this chapter is defined as a custom MXML component named ContactsGrid.mxml
in the project's src/view
folder.
Listing 26.4 shows the code for the CFC that's called by this and other applications in this section. Notice that the Query
object is created manually in ColdFusion code, rather than being generated with a <cfquery>
command. As a result, the case of the column names is controlled by this call to the ColdFusion QueryNew()
function, rather than being derived from a database query's metadata:
<cfset var qContacts=queryNew('contactId,firstname,lastname,city')>
Example 26.4. A CFC returning a Query object
<cfcomponent name="ContactService" hint="Delivers Contact data from an XML file to a Flex application"> <cfapplication name="flex3BibleChapter26" sessionManagement="true"> <!--- Initialize data set in memory if it doesn't already exist ---> <cfif not structKeyExists(session, "qContacts")> <cfset createDataSet()> </cfif> <!--- Returns all data from a query object ---> <cffunction name="getAllContacts" returntype="query" access="remote"> <cfreturn session.qContacts> </cffunction> <!--- Returns filtered data using query of query --->
<cffunction name="getFilteredContacts" returnType="query" access="remote"> <cfargument name="firstname" type="string" required="true"> <cfargument name="lastname" type="string" required="true"> <cfset var qFiltered=""> <cfquery dbtype="query" name="qFiltered"> SELECT * FROM session.qContacts WHERE 0=0 <cfif len(trim(firstname))> AND firstname LIKE '%#trim(arguments.firstname)#%' </cfif> <cfif len(trim(lastname))> AND lastname LIKE '%#trim(arguments.lastname)#%' </cfif> </cfquery> <cfreturn qFiltered> </cffunction> <!--- Returns the total count of Contacts ---> <cffunction name="getContactCount" returntype="numeric" access="remote"> <cfreturn session.qContacts.recordCount> </cffunction> <!--- Called to create a query object from an XML file ---> <cffunction name="createDataSet" returntype="void" access="private"> <cfset var strContacts=""> <cfset var xContacts=""> <cfset var i=""> <cfset var qContacts=queryNew('contactId,firstname,lastname,city')> <cffile action="read" file="#expandPath('data/contacts.xml')#" variable="strContacts"> <cfset xContacts=xmlParse(strContacts)> <cfloop from="1" to="#arrayLen(xContacts.contacts.row)#" index="i"> <cfset QueryAddRow(qContacts)> <cfset qContacts.firstname[i]=xContacts.contacts.row[i] .firstname.xmltext> <cfset qContacts.lastname[i]=xContacts.contacts.row[i].</p> lastname.xmltext> <cfset qContacts.contactId[i]=xContacts.contacts.row[i].</p> contactId.xmltext> <cfset qContacts.city[i]=xContacts.contacts.row[i].city.xmltext> </cfloop> <cfset session.qContacts=qContacts> </cffunction> </cfcomponent>
The code in Listing 26.4 is available in the Web site files as ContactService.cfc
in the ColdFusion files of the chapter26
project.
When you need to call more than one function from a CFC, you have to distinguish which event handler function should be called for each of them. You do this in MXML with the <mx:method >
compiler tag, which is nested within a <mx:RemoteObject>
tag set. Each <mx:method >
tag represents a CFC function and can declare its own distinct result
and fault
event handlers.
The CFC in Listing 26.4, for example, has a function named getContactCount()
that returns a numeric value and a function named getAllContacts()
that returns a Query object (translated to an ArrayCollection
in the Flex application). To handle each function's result
event with its own distinct event handler function, you create the functions and then declare the <mx:method>
tags as follows:
<mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactService"> <mx:method name="getContactCount" result="countHandler(event)"/> <mx:method name="getAllContacts" result="dataHandler(event)"/> </mx:RemoteObject>
The application in Listing 26.5 calls getContactCount()
upon application startup to inform the user how many data items are available on the server. The call to getAllContacts()
to actually retrieve the data is made only when the user clicks Get Contact Data.
Example 26.5. Handling result events from multiple CFC functions
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" xmlns:view="view.*" backgroundColor="#EEEEEE" creationComplete="contactService.getContactCount()"> <mx:Script> <![CDATA[ import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; [Bindable] private var recordCount:Number; [Bindable] private var contactData:ArrayCollection; private function countHandler(event:ResultEvent):void { recordCount = event.result as Number; } private function dataHandler(event:ResultEvent):void { contactData = event.result as ArrayCollection;
} ]]> </mx:Script> <mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactService"> <mx:method name="getContactCount" result="countHandler(event)"/> <mx:method name="getAllContacts" result="dataHandler(event)"/> </mx:RemoteObject> <mx:Label text="There are {recordCount} Contacts available on the server"/> <mx:Button label="Get Contact Data" click="contactService.getAllContacts()"/> <view:ContactsGrid dataProvider="{contactData}"/> </mx:Application>
The code in Listing 26.5 is available in the Web site files as ROMultipleFunctions.mxml
in the src
folder of the chapter26
project.
Figure 26.4 shows the resulting application. The Label
at the top of the application displays the getContactCount()
results immediately upon application startup, while the actual data is displayed only when the user requests it from the server.
As with other RPC components, you can also handle result
and fault
events using the ItemResponder
and AsyncToken
classes. For a description of this pattern and sample code, see Chapter 21.
You can pass arguments to CFC functions in three different ways:
Explicit arguments are passed in the order in which they're declared in the CFC function
Bound arguments are declared with MXML code and bound to data sources in the Flex application
Named arguments are wrapped in an ActionScript Object and are passed as though the CFC function expects only a single argument.
Explicit arguments are passed in the same order in which they're declared in the CFC function. The CFC's getFilteredContacts()
function declares two required arguments:
<cffunction name="getFilteredContacts" returnType="query" access="remote"> <cfargument name="firstname" type="string" required="true"> <cfargument name="lastname" type="string" required="true"> ... function body ... </cffunction>
Using explicit arguments, you pass values that match the expected data types. This code sends the data in the same order in which they're declared:
contactService.getFilteredContacts(fnameInput.text, lnameInput.text);
It's also possible to pass arguments explicitly with the Operation class's send()
method. The following syntax allows you to pass remote CFC function names as strings or variables, passed into the RemoteObject
component's getOperation()
method:
contactService.getOperation("getFilteredContacts").send( fnameInput.text, lnameInput.text);
This calling syntax also works with the WebService
component.
Bound arguments are declared in MXML and then passed through a call to the RemoteObject
component's send()
method. Just as when using the RemoteObject
component to call Java-based methods in classes hosted by BlazeDS, you use bound argument notation with XML elements for each argument wrapped in an <mx:arguments>
tag set. This code binds the getFilteredContacts()
method's two arguments to values gathered from TextInput
controls:
<mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactService"> <mx:method name="getFilteredContacts"> <mx:arguments> <firstname>{fnameInput.text}</firstname > <lastname>{lnameInput.text}</lastname> </mx:arguments> </mx:method> </mx:RemoteObject>
To call the method with the bound arguments, call the operation's send()
method without any explicit arguments:
contactService.getFilteredContacts.send()
When using bound arguments with CFC functions, the arguments are matched by name, not by the order of declaration in the client-side code. This behavior is different from Java-based methods and arguments, where bound arguments are sent in the order in which they're declared, and the XML element names are ignored.
You can match CFC function arguments by name by wrapping them in an ActionScript Object
. Each property has a name and a value; the name of the Object
property must match the name of the CFC function argument.
This code creates an Object
and attaches argument values by name:
var args:Object = new Object(); args.firstname = fnameInput.text; args.lastname = lnameInput.text; contactService.getFilteredContacts(args);
You also can write the same code using shorthand Object
notation:
contactService.getFilteredContacts( {firstname:fnameInput.text, lastname:lnameInput.text});
This behavior is sometimes confusing to ColdFusion developers who first encounter it, because it means that you can't pass an anonymous Object
as an argument to a CFC function and expect it to arrive intact as a ColdFusion structure. The Object
is always broken down into its named properties, and then the individual properties are passed to the CFC function. If you need to pass a structure argument to a CFC function, you have to wrap it in another Object
and pass it by name:
myService.myFunction({myArgumentName:myObject});
The named arguments behavior in Flex is very similar to ColdFusion's own argumentCollection
attribute, which can be used to pass a structure of named arguments to functions from CFML code. This CFML code calls a CFC function and passes a structure in very much the same way:
<cfset stArgs=structNew()> <cfset stArgs.firstname="firstnameValue"> <cfset stArgs.lastname="lastnameValue"> <cfinvoke component="flex3bible.chapter26.ContactService" argumentCollection="#stArgs#" returnVariable="qContacts"/>
The application in Listing 26.6 uses named arguments wrapped in an ActionScript Object
.
Example 26.6. An application passing named arguments to a CFC function
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE" xmlns:view="view.*"> <mx:Script> <![CDATA[ private function getContacts():void { var args:Object = new Object(); args.firstname = fnameInput.text; args.lastname = lnameInput.text; contactService.getFilteredContacts(args); } ]]> </mx:Script> <mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactService"/> <mx:Form> <mx:FormItem label="First Name:"> <mx:TextInput id="fnameInput"/> </mx:FormItem> <mx:FormItem label="Last Name:"> <mx:TextInput id="lnameInput"/> <mx:Button label="Get Filtered Data" click="getContacts()"/> </mx:FormItem> </mx:Form> <view:ContactsGrid dataProvider="{contactService.getFilteredContacts.lastResult}"/> </mx:Application>
The code in Listing 26.6 is available in the Web site files as RONamedArgs.mxml
in the src
folder of the chapter26 project. Examples of the same application using explicit and bound arguments are in ROExplicitArgs.mxml
and ROBoundArgs.mxml
, respectively.
When passing data between a Flex client application and ColdFusion, you can build both clientside and server-side data objects using the Value Object design pattern. The Flex version is built as an ActionScript class, while the ColdFusion version is built as a CFC. At runtime, you can pass strongly typed value objects between the client and server tiers of your application, and the Flex application and the Flash Remoting gateway automatically transfer data between the objects based on a mapping that you provide in the code.
The Value Object design pattern is also known in various industry documentation sources as the Transfer Object and Data Transfer Object pattern. The different names are all used to refer to the same pattern: a class that contains data for a single instance of a data entity.
The ColdFusion version of a value object is written as a simple CFC. The <cfcomponent
> start tag requires an alias attribute that's set to the fully qualified name and location of the CFC:
<cfcomponent output="false" alias="flex3Bible.chapter26.Contact"> ... component body ... </cfcomponent>
Each named property is declared after the <cfcomponent
> start tag using the <cfproperty
> tag. Each property has a name and a type to indicate how it will be exchanged with Flex. The following <cfproperty
> tags declare one numeric and two string properties:
<cfproperty name="contactId" type="numeric" default="0"> <cfproperty name="firstname" type="string" default=""> <cfproperty name="lastname" type="string" default="">
The <cfproperty
> tag, which in conventional ColdFusion code is used only to generate CFC documentation, controls the name and case of the property name when it's exchanged with the Flex application at runtime. This overrides the settings in the configuration files that control the case of property names.
The <cfproperty
> tag's default
attribute is used to generate CFC documentation and doesn't actually set default values when the CFC is instantiated in ColdFusion. Instead, you typically add code outside any function definitions that set the properties' default values upon instantiation:
<cfscript> this.contactId = 0; this.firstname = ""; this.lastname = ""; </cfscript>
The CFC in Listing 26.7 is a value object that declares four properties of a Contact
value object and sets their initial values upon instantiation.
Example 26.7. A simple value object CFC
<cfcomponent output="false" alias="flex3Bible.chapter26.Contact"> <cfproperty name="contactId" type="numeric" default="0"> <cfproperty name="firstname" type="string" default=""> <cfproperty name="lastname" type="string" default=""> <cfproperty name="city" type="string" default=""> <cfscript> this.contactId = 0; this.firstname = ""; this.lastname = ""; this.city = "" </cfscript> </cfcomponent>
The code in Listing 26.7 is available in the Web site files as Contact.cfm
in the ColdFusion files of the chapter26
project.
The Flex client application uses an ActionScript version of the value object built as an ActionScript class. The class requires a [RemoteClass]
metadata tag with an alias attribute that describes the fully qualified name and location of the matching CFC:
[RemoteClass(alias="flex3Bible.chapter26.Contact")]
This is a two-way mapping: When an ActionScript version of the object is sent to ColdFusion, the Flash Remoting gateway creates an instance of the CFC and passes the received object's property values to the server-side version. Similarly, if a CFC function returns instances of the server-side version, client-side versions are created automatically and their property values set to the values received from the server.
The alias attributes
of the [RemoteClass]
metadata tag on the client and the <cfcomponent
> tag on the server must match exactly and are case-sensitive.
The ActionScript class in Listing 26.8 declares the same set of values as public properties and maps itself to the server's version with the [RemoteClass]
metadata tag.
Example 26.8. An ActionScript value object class
package vo { [Bindable] [RemoteClass(alias="flex3Bible.chapter26.Contact")] public class Contact { public var contactId:int; public var firstname:String; public var lastname:String; public var city:String; public function Contact() { } } }
The code in Listing 26.8 is available in the Web site files as Contact.as
in the src/vo
folder in the chapter26
project.
After you've built versions of the value object in both ColdFusion and ActionScript and provided the appropriate mappings, a CFC function has the ability to return either individual value object instances or collections of value objects wrapped into arrays. This CFC function creates an instance of the Contact value object and returns it to Flex:
<cffunction name="getContactVO" access="remote" returntype="flex3Bible.chapter26.Contact"> <cfset contact=createObject("component", "flex3Bible.chapter26.Contact")> <cfset contact.contactId = 1 > <cfset contact.firstname = "David"> <cfset contact.lastname = "Gassner"> <cfset contact.city = "Seattle"> <cfreturn contact> </cffunction>
Notice that the function's returntype
attribute is set to the fully qualified name and location of the value object CFC.
The CFC in Listing 26.9 extends the original CFC and uses its data, but transforms the query object into an array of strongly typed value objects before returning it to Flex. Notice that this time the CFC function's returntype
attribute is set to an array of CFC instances, rather than the usual generic array
notation.
Example 26.9. A CFC function returning an array of value objects
<cfcomponent name="ContactServiceWithVO" hint="Delivers Contact data as an array of value objects" extends="ContactService"> <cfapplication name="flex3BibleChapter26" sessionManagement="true"> <cfif not structKeyExists(session, "qContacts")> <cfset createDataSet()> </cfif> <!--- Returns all data as an array of structures ---> <cffunction name="getContactsAsArray" access="remote" returntype="flex3Bible.chapter26.Contact[]"> <cfset var contact=""> <cfset var arReturn=arrayNew(1)> <cfloop query="session.qContacts"> <cfset contact=createObject("component", "flex3Bible.chapter26.Contact")> <cfset contact.contactId = session.qContacts.contactId> <cfset contact.firstname = session.qContacts.firstname> <cfset contact.lastname = session.qContacts.lastname> <cfset contact.city = session.qContacts.city> <cfset arrayAppend(arReturn, contact)> </cfloop> <cfreturn arReturn> </cffunction> </cfcomponent>
The code in Listing 26.9 is available in the Web site files as ContactServiceWithVO.cfc
in the ColdFusion files of the chapter26
project.
In order to receive value objects from a CFC function and have them automatically transformed into instances of the ActionScript value object, the Flex application must contain at least one reference to the ActionScript class. The reference can be a declared instance of the class or a call to any static properties or methods. This ensures that the class, which contains the [RemoteClass]
metadata tag, is compiled into the application and is available at runtime.
It isn't enough to just import the value object class in the Flex application; you have to declare at least one instance. The purpose of importing a class is to inform the Flex compiler of the class's existence, but the compiler doesn't include the class definition in the binary version of the application unless at least one instance of the class is declared, or there's a reference to one of its static members.
The Flex application in Listing 26.10 receives the Array of value objects and processes them in an event handler. Notice the MXML <vo:Contact/
> declaration that ensures the mapping of the value object classes is included in the compiled application.
When a CFC function returns a ColdFusion array
, it's received in Flex as an ActionScript Array
, not an ArrayCollection
. In this application, the result
event handler expects an Array
and assigns it to the source of the already-instantiated ArrayCollection
:
contactData.source = event.result as Array;
Example 26.10. Receiving an Array of strongly typed value objects
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE" creationComplete="initApp()" xmlns:view="view.*" xmlns:vo="vo.*"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.collections.ArrayCollection; import mx.rpc.events.ResultEvent; import mx.rpc.remoting.RemoteObject; [Bindable] private var contactData:ArrayCollection = new ArrayCollection(); private var contactService:RemoteObject; private function initApp():void { contactService = new RemoteObject("ColdFusion"); contactService.source = "flex3bible.chapter26.ContactServiceWithVO"; contactService.addEventListener(ResultEvent.RESULT, resultHandler); } private function resultHandler(event:ResultEvent):void { contactData.source = event.result as Array; } ]]> </mx:Script> <vo:Contact/> <mx:Button label="Get Contacts" click="contactService.getContactsAsArray()"/> <view:ContactsGrid dataProvider="{contactData}"/> </mx:Application>
The code in Listing 26.10 is available in the Web site files as ROReceiveValueObjects.mxml
in the src folder of the chapter26 project.
Value objects also can be passed from a Flex client application to a CFC function. The CFC function should have declared an argument typed as the ColdFusion version of the value object. This CFC function receives an instance of the Contact
value object CFC and returns a concatenated string built from its properties:
<cffunction name="parseContact" access="remote">' <cfargument name="contactVO" type="flex3Bible.chapter26.Contact" required="true"> <cfreturn "Contact received: " + contactVO.firstname + " " + contactVO.lastname> </cffunction>
To pass a value object argument to the CFC function from Flex, create an instance of the ActionScript version of the object and set its properties. Then pass the value object to the function using any of the argument-passing strategies described previously.
The application in Listing 26.11 passes an instance of the ActionScript value object to a CFC function that extracts its properties and returns a concatenated string.
Example 26.11. Passing a value object to a CFC function
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE"> <mx:Script> <![CDATA[ import vo.Contact; private function passArgument():void { var newContact:Contact = new Contact(); newContact.firstname = fnameInput.text; newContact.lastname = lnameInput.text; contactService.parseContact(newContact); } ]]> </mx:Script> <mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactServiceWithVO"/> <mx:Panel title="Enter Contact Information">
<mx:Form> <mx:FormItem label="First Name:"> <mx:TextInput id="fnameInput"/> </mx:FormItem> <mx:FormItem label="Last Name:"> <mx:TextInput id="lnameInput"/> <mx:Button label="Pass Argument" click="passArgument()"/> </mx:FormItem> </mx:Form> </mx:Panel> <mx:Label text="{contactService.parseContact.lastResult}" fontSize="12"/> </mx:Application>
The code in Listing 26.11 is available in the Web site files as ROPassVOArg.mxml
in the src
folder of the chapter26
project.
When an exception occurs during a call to a CFC function, the RemoteObject
dispatches a fault
event. The event object is typed as mx.rpc.events.FaultEvent
and contains a fault property typed as mx.rpc.Fault
. This object in turn has String properties named faultCode, faultString
, and faultDetail
. The values of these properties differ depending on the nature of the error, and in the case of faultDetail
they sometimes don't contain useful information.
As with all events, you can create an event listener with either MXML or ActionScript code. The MXML attribute-based event listener looks like this:
<mx:RemoteObject id="contactService" destination="ColdFusion" source="flex3bible.chapter26.ContactServiceWithVO" fault="faultHandler(event)"/>
To create an event listener in ActionScript code, call the RemoteObject
component's addEventListener
() method and declare the event name using the FaultEvent.FAULT
constant:
faultService.addEventListener(FaultEvent.FAULT, faultHandler);
When the event handler function receives the event object, you can handle it in any way you like. Minimally, you might display the fault information to the user with a pop-up dialog box generated by the Alert class:
private function faultHandler(event:FaultEvent):void { Alert.show(event.fault.faultString, event.fault.faultCode); }
In ColdFusion 8, the value of the error message always has a prefix of "Unable to invoke CFC -". If you don't want to display this prefix, you have to parse the original error message from the value of the faultString
property:
var errorMessage:String = event.fault.faultString; errorMessage = errorMessage.substring(22, errorMessage.length); Alert.show(errorMessage, event.fault.faultCode);
On the server, you can generate your own faults from a CFC function by calling the ColdFusion <cfthrow>
command. These <cfthrow>
commands' attributes are exposed in the Flex application's <FaultEvent.fault>
object:
The message attribute appears in the fault
object's faultString
property.
The errorcode
attribute appears in the fault object's faultCode
property.
The CFC in Listing 26.12 implements a throwCFCFault
() function that always generates a fault with message
and errorcode
attributes.
Example 26.12. A CFC function generating a server-side fault
<cfcomponent output="false"> <cffunction name="throwCFCFault" returntype="String"> <cfthrow message="An error message generated by a CFC function" errorcode="CFC Function Error"> <cfreturn "A String"> </cffunction> </cfcomponent>
The code in Listing 26.12 is available in the Web site files as FaultService.cfc
in the ColdFusion files of the chapter26
project.
The Flex application in Listing 26.13 calls the CFC function to intentionally generate a fault and display its information in an Alert
pop-up dialog box.
Example 26.13. A Flex application handling a server-side fault from a CFC function
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" backgroundColor="#EEEEEE"> <mx:Script> <![CDATA[ import mx.controls.Alert; import mx.rpc.events.FaultEvent; import mx.rpc.events.ResultEvent; import mx.rpc.remoting.RemoteObject; [Bindable] private var returnString:String; private function resultHandler(event:ResultEvent):void { returnString = event.result as String; } private function faultHandler(event:FaultEvent):void { var errorMessage:String = event.fault.faultString; errorMessage = errorMessage.substring(22, errorMessage.length); Alert.show(errorMessage, event.fault.faultCode); } ]]> </mx:Script> <mx:RemoteObject id="faultService" destination="ColdFusion" source="flex3bible.chapter26.FaultService" result="resultHandler(event)" fault="faultHandler(event)"/> <mx:Button label="Generate Fault" click="faultService.throwCFCFault()"/> <mx:Label text="{returnString}"/> </mx:Application>
The code in Listing 26.13 is available in the Web site files as ROFaultHandler.mxml
in the src
folder of the chapter26
project.
In this chapter, I described how to integrate Flex client applications with Adobe ColdFusion 8 using Flash Remoting and the Flex framework's RemoteObject
component. You learned the following:
Flash Remoting was originally introduced with ColdFusion MX and was adapted for use in LiveCycle Data Services and Blaze as the Remoting Service.
Flash Remoting allows you to call functions of ColdFusion components (CFCs) from a ColdFusion server.
Remote function calls and responses are encrypted in AMF, a binary message format that's significantly smaller and faster than XML.
Data can be exchanged between the Flex client and a CFC function based on documented data type mappings.
Calls to CFC functions are asynchronous.
CFC function results can be handled with binding expressions or by handling the RemoteObject
component's result
event.
Arguments can be passed to CFC functions using explicit, named, or bound argument syntax.
Strongly typed value objects can be created in both ActionScript and ColdFusion and exchanged automatically between client and server at runtime.
Exceptions are handled as Flex application faults using the RemoteObject
component's fault event.
Custom exceptions can be generated in ColdFusion and handled in a Flex client application.
3.128.198.60