This tutorial assumes that you have already installed the following:
The ColdFusion Flex 2 Adapter comes with installation instructions that must be followed very carefully. (Most initial problems are the result of the Adapter not be installed or configured correctly).
The Application
The application that you will be building is a simple data drill-down application which uses the sample data that is installed with ColdFusion MX 7.0.1. On startup, the application displays a drop down combo box containing a list of artists. Selecting any artist displays that artist's art, and clicking on art items displays an image.
Setup
Create a folder for your new application under your web root. The default I am using in this tutorial has a folder named flex under the web root, and a folder for this application named ArtCatalog in the flex folder. (On a default installation on a Windows machine this will therefore be
c:\cfusionmx7\wwwroot\flex\artcatalog).
The Backend
The backend for this application is a simple ColdFusion Component named
art.cfc which contains two methods.
GetArtists() returns the artist list, and
GetArt() accepts an
artistid and returns that artist's art. The CFC itself is very simple:
<cfcomponent>
<!--- Get artists --->
<cffunction name="GetArtists" access="remote" returntype="query" output="false">
<cfset var artists="">
<cfset var result="">
<cfquery datasource="cfcodeexplorer" name="artists">
SELECT RTrim(lastname) + ', ' + RTrim(firstname) AS artist,
artistid
FROM artists
ORDER BY lastname, firstname
</cfquery>
<cfquery dbtype="query" name="results">
SELECT artist AS label, artistid AS data
FROM artists
ORDER BY label
</cfquery>
<cfreturn results>
</cffunction>
<!--- Get art for specified artist --->
<cffunction name="GetArt" access="remote" returntype="query" output="false">
<cfargument name="artistid" type="numeric" required="yes">
<cfset var art="">
<cfset var result="">
<cfquery datasource="cfcodeexplorer" name="art">
SELECT artid, artname, description, issold,
largeimage, mediaid, price
FROM art
WHERE artistid=<cfqueryparam value="#ARGUMENTS.artistid#"
cfsqltype="cf_sql_numeric">
ORDER BY artname
</cfquery>
<cfquery dbtype="query" name="results">
SELECT artid, artname, description, issold,
'http://localhost:8500/cfide/gettingstarted/tutorial/images/' + largeimage AS image,
mediaid, price
FROM art
ORDER BY artname
</cfquery>
<cfreturn results>
</cffunction>
</cfcomponent>
Notice that both methods query for data, and then query that query. There are two reasons for this. One, there is a bug in the current adapter whereby database query column names may not be converted to lowercase properly, querying the query solves this problem. Two, some Flex controls (like <mx:ComboBox>) have specific column name requirements, and the second query in
GetArtists() renames the columns to the naming convention needed by that control.
It is generally a good idea to thoroughly test CFCs using simple ColdFusion calls before using them in your Flex apps. The following file named
arttest.cfm can be used to test that
art.cfc is functioning properly:
<cfinvoke component="art"
method="GetArtists"
returnvariable="artists">
<ul>
<cfoutput query="artists">
<li><a href="#CGI.SCRIPT_NAME#?artistid=#data#">#label#</a></li>
</cfoutput>
</ul>
<cfif IsDefined("URL.artistid")>
<cfinvoke component="art"
method="GetArt"
artistid="#URL.artistid#"
returnvariable="art">
<hr />
<cfdump var="#art#">
</cfif>
Defining The Service
Flex 2 applications access ColdFusion Components via Flash Remoting using a service name. This name must be defined in an XML file, mapping a destination name to a source (the path to the CFC).
When you installed the ColdFusion Adapter you copied a file named
coldfusionsamples.xml into the
WEB-INF\flex folder. Open that file, and add the following destination definition. The source here specifies
flex\artcatalog\art as the path to the CFC (the .cfc extension should not be included), if you are using a different path make the necessary changes to the source.
<destination id="ArtCatalog">
<channels>
<channel ref="my-cfamf"/>
</channels>
<properties>
<source>flex.artcatalog.art</source>
<lowercase-keys>true</lowercase-keys>
</properties>
</destination>
Once you have made the change and saved the xml file, you will need to restart ColdFusion.
Creating The Project
Here are the steps needed to create the new Flex Builder project:
- Open Flex Builder 2.
- Select File, New, Flex Project.
- Specify ArtCatalog as the project name.
- Specify the project location (you can use the same folder as where you created art.cfc).
- The default Main Application File and Output Folder are ok, so click Finish to create the project.
- Right click on the new project in the Navigator panel, and select Properties to open the project Properties dialog.
- Select Flex Compiler.
- In the Additional compiler arguments field enter: -services=C:\CFusionMX7\wwwroot\WEB-INF\flex\flex-services.xml (or modify the path to match your installation).
- Click OK.
Building The Application
Now you can start building the application. The first thing you need to do is tell Flex how to find your CFC. You do this using the <mx:RemoteObject> tag, as follows:
<!-- ColdFusion CFC (via AMF) -->
<mx:RemoteObject id="artSvc" destination="ArtCatalog" showBusyCursor="true" />
Now, whenever you refer to artSvc Flex will be able to connect to art.cfc using the previously created mapping.
Next you'll define the artist drop down combo box. Here is the code (make sure to place it between the <mx:Application> tags, after the <mx:RemoteObject> tag):
<!-- Main display -->
<mx:VBox width="100%" height="100%">
<!-- Select artist -->
<mx:FormItem label="Artist:">
<mx:ComboBox id="artistsCB" dataProvider="{artSvc.GetArtists.result}"
width="150" />
</mx:FormItem>
</mx:VBox>
Save the code and run the application (click on the run button, the one with the white arrow in a green circle). The application should run, but the combo-box will not be populated . This is actually the correct behavior, you never called the CFC method, so there were no results to populate it with.
You want the
GetArtists() method to be invoked automatically on application startup. To do that, create a ActionScript function named
InitApp() as seen here:
<mx:Script>
<![CDATA[
public function InitApp():Void
{
artSvc.GetArtists();
}
]]>
</mx:Script>
Next, modify the <mx:Application> tag. Go to the end of the tag and press space, you should see a popup of all available properties and methods. Select the property named
creationComplete and specify
InitApp() as the value. The opening tag should now look like:
<mx:Application xmlns:mx="http://www.macromedia.com/2005/mxml" xmlns="*"
creationComplete="InitApp()">
Save the code and run it again. This time the combo box should be automatically populated because the
dataProvider points to
artSvc.GetArtists.result (the result returned by the call to
GetArtists in the
artSvc service).
Next you'll create the data grid to display artist's art. Here is the code, place it after the tag:
<!-- Results -->
<mx:HBox width="100%" height="100%">
<!-- Results grid -->
<mx:DataGrid height="100%" width="100%" id="artGrid"
dataProvider="{artSvc.GetArt.result}">
<mx:columns>
<mx:DataGridColumn headerText="Name" columnName="artname" />
<mx:DataGridColumn headerText="Description" columnName="description" />
<mx:DataGridColumn headerText="Sold" columnName="issold" />
<mx:DataGridColumn headerText="Price" columnName="price" />
</mx:columns>
</mx:DataGrid>
</mx:HBox>
This code creates a grid containing four columns. (The grid columns could have been omitted, but then all columns would have been displayed, and you'd also have lost the ability to fine-tune the appearance of specific columns as you will see shortly). The grid's
dataProvider points to the result of
artSvc.GetArt().
Which means that method must be invoked. Modify the <mx:ComboBox> and add the following property and value:
change="{artSvc.GetArt(artistsCB.selectedItem.data)}"
This way, any time the combo box changed,
GetArt() will be called.
GetArt() requires that an argument be passed to it, the id of the artist who's art you wish to obtain. This argument is passed to
GetArt() as
artistsCB.selectedItem.data (the data value for the currently selected item in
artistsCB).
Now save and run the application. You should be able to select any artist to populate the data grid with his or her art.
Now add the following code so as to display an image of the art when an art item is selected in the grid. Place it after the </mx:DataGrid> tag and before the closing <mx:HBox> tag:
<!-- Image panel -->
<mx:Panel title="{artGrid.selectedItem.artname}">
<mx:Image height="200" width="200" source="{artGrid.selectedItem.image}" />
</mx:Panel>
This code creates a panel, setting the title to the artname currently slected in the data grid, and populate the source on an <mx:Image> tag with the image URL as returned by
GetArt().
Using Cell Renderers
The default display in the data grid is fine for simple text data, but there are two columns that don't look right here. The
Sold column is displaying
true or
false, which is not very intuitive. And the
Price column is not formatting the values as currency values. Both of these problems can easily be addressed using cell renderers. Basically, a cell renderer is code that Flex should use to render cells instead of any default rendering.
The
Sold column could use a checkbox to display a check if sold and not if not. To do this, simply add the following property to the appropriate <mx:DataGridColumn> tag:
cellRenderer="mx.controls.CheckBox"
If you run the application now you'll see that a checkbox is displayed, and will automatically be checked if true and not if false.
To format the
Price column you will use a custom cell renderer, a new component. To create a new component do the following:
- Right click on the ArtCatalog project in the Navigator panel.
- Select New, MXML Component.
- Specify ArtPrice as the File name, and select VBox as the Base component.
- Click Finish.
Flex Builder will create a new component (based on <mx:VBox>) for you to use. When a component is called as a cell renderer, an object named
dataObject is passed to it containing the row to be rendered.
Add the following code in between the <mx:VBox> tags:
<mx:CurrencyFormatter id="cf"/>
<mx:Label text="{cf.format(dataObject.price)}" />
<mx:CurrencyFormatter> creates a formatter that knows how to format values as currency values. The <mx:Label> tag simply uses that formatter to display
dataObject.price (the price column of the current row).
The last thing to do is to tell Flex to use this new cell renderer. Go back to
ArtCatalog.mxml and modify the price
<mx:DataGridColumn> so that it includes the following:
cellRenderer="ArtPrice"
That's it. Save the files, and run the application. You can now select an artist, display art (formatted using cell renderers), and click on any art item to see it's image (not all of the items have associated images).
Application Source Code
This
downloadable zip file contains the ColdFusion and Flex source code.
Summary
This application demonstrates basic ColdFusion Flex 2 integration, passing data to and from ColdFusion, as well as basic bindings and cell renderers.
Watch for more complex and powerful examples coming soon.
You can skip this step by using the ComboBox 'labelField' attribute (http://livedocs.macromedia.com/flex/15/asdocs_en/m...)
Ray, I'll fix that one, thanks.
<cfset var art="">
<cfset var result="">
should be
<cfset var art="">
<cfset var results="">
right?
tony
Man, I LOVE Flex and how good it's going to make my projects look! We (at my company) are planning to completely redo our corporate website based around a new Flex interface... we already use ColdFusion, so intergration'll be a snap!
I didn't download the files you provided.
Instead I followed along, and everything ran smoothly, without any modifications or changes needed.
Thanks for helping me get started in Flex.
And please keep the tutorials coming, I look forward to the intermediate level.
Ali
Two minor details worth mentioning:
1) the flex compiler argument syntax has one leading dash in this blog entry, but on the labs.macromedia.com CF Adaptor page it has two leading dashes. Not sure if this is important.
2) The flex.artcatalog.art GetArt method refers to a hard-coded url, so folks might want to adjust to fit their system to get the images to display. In my case I have CFMX 6.1 and 7 on the same machine, so CF7 listens on 8501 rather than 8500.
Thanks!
Any idea where to look to check my setup?
I have tried the arttest.cfm and pasted the URL's which all work fine, it seems as though the image tag in flex is broke on my machine :( as I am having similar problems with even the simplist flex page.
Steve, All that should need to change is the location of the files and the URL for images. Is the test .cfm file executing the CFC properly? What path are all the files in? And what did you specify in the XML file?
I hope this is understandable.
The error I receive is "Couldn't establish connection to 'ArtCatalog'".
test.cfm work perfectly
the file path on my webserver is C:\inetpub\wwwroot\flexprojects\artcatalog
The coldfusionsamples.xml file (still located in the CF7 WEB-INF folder):
<properties>
<source>flexprojects.artcatalog.art</source>
<lowercase-keys>true</lowercase-keys>
</properties>
One thing I have noticed is that when you set "-services=C:\CFusionMX7\wwwroot\WEB-INF\flex\flex-services.xml" in the the Flex Builder it is relative to your workspace and not the actual deployed file. Since I am using a network drive (P) for my web server I set this to read "-service=P:\CFusionMX7\wwwroot\WEB-INF\flex\flex-services.xml". Which removed the error from my workspace, but neither resolved my issue.
This tutorial was just what I needed, thanks !
I've got a problem though. The data is not populated when I run the application. If I click on the column labels I get the message below. I'm new to Flex and also a beginner of ColdFusion so I don't really understand what this error message says.
TypeError: Error #1006: Call attempted on an object that is not a function.
at mx.collections::Sort/Sort$257$private::internalCompare()
at mx.collections::Sort/findItem()
at mx.collections::ListCollectionView/getItemIndex()
at mx.collections::ListCollectionViewCursor/ListCollectionViewCursor$1502$private::collectionEventHandler()
at flash.events::EventDispatcher/dispatchEvent()
at mx.collections::ListCollectionView/dispatchEvent()
at mx.collections::ListCollectionView/refresh()
at mx.collections::ListCollectionView/set sort()
at mx.controls.listclasses::ListBase/sortItemsBy()
at mx.controls::DataGrid/sortByColumn()
at mx.controls::DataGrid/mouseUpHandler()
I have the same setup, my flex-services.xml file is on another server, not my local machine.
This is what I do, and it works for me:
firstly I use 2 "-"'s in front of services, then I use a UNC path, NOT the network drive number.
"--services=\\<network computer id>\c$\CFusionMX7\wwwroot\WEB-INF\flex\flex-services.xml"
Thanks. Fixed one problem. Still have the primary error.
Any idea when the connector column name bug will be fixed, bit of a pain having to always do a query of queries.
Ex : SELECT firstname AS firstname, lastname AS lastname from tblUsers ...
I m also not sure all databases support aliasing with the same name as the column
Philippe, that may still return it in uppercase, unfortunately.
I'm trying ArtCatalog...
Where can I get [cfcodeexplorer] ??
Is this MDB ??
java.lang.RuntimeException: org/apache/xpath/CachedXPathAPI
on resource: ArtCatalog
location: Line -1
And when I run the example the page opens, the Flash progress bar shows up, but then nothing happens or renders. Any idea what this could be?
I ran into the same runtime exception (running CF multi server/Apache). In addition, firing up regular CF apps with mapped cfc's throws returntype errors. Raymond Camden has blogged about the returntype issue and apparently a fix is in the pipe. Not sure about the CachedXPathAPI error though.
The arttest.cfm is working.
The page ArtCatalog.html comes up but with no data and the clock icon pointer just spinning away. Down in the status bar it says "Connecting to 10.3...." the server.
I must not have the CF Adapter set up right. Any way to test.
Thanks in advance.
What looks like a relevant error while sartup of the service is:
---------------
java.lang.ClassNotFoundException
at coldfusion.bootstrap.BootstrapClassLoader.loadClass(BootstrapClassLoa
der.java:229)
AND
1/15 07:59:25 user failed to load: flex.messaging.MessageBrokerServlet
11/15 07:59:25 error Could not pre-load servlet: MessageBrokerServlet
[1]java.lang.ClassNotFoundException
at coldfusion.bootstrap.BootstrapClassLoader.loadClass(BootstrapClassLoa
der.java:229)
at java.lang.ClassLoader.loadClass(Unknown Source)
at coldfusion.bootstrap.ClassloaderHelper.initServletClass(ClassloaderHe
lper.java:95)
----------------
How do I fix this?
Am I missing a CF update
This is the Sys Info out of CF Admin
System Information
Server Details
Server Product ColdFusion MX
Version 7,0,1,116466
Edition Enterprise (DevNet)
Serial Number
Operating System Windows 2003
OS Version 5.2
Update Level /C:/CFusionMX7/lib/updates/hf701-61119.jar
------------------------------------------
"Violation*** localhost:8500/flex2gateway/ halted - not permitted from file (file path)"
------------------------------------------
Is there a reason the flex file wouldn't be able to retrieve data from a cfc referencing a SQL Server Datasource?
Jeff, no, the type of data source makes no differance.
The arttest.cfm returns the artists names and clicking the artists name brings up the query with appropriate results.
When I first downloaded the zip file and tried out the example, I had the same "Cannot Connect" problem.
The problem seems to be the placement of the <mx:RemoteObject> tag. This tag needs to be at the bottom of the main mxml file. Once I put it down at the bottom, it works.
Note: for some reason, after moving it to the bottom, I was able to connect. After that, I could move the <mx:RemoteObject> tag anywhere else on the page and it will still connect.
I tried another experiment where I deleted the entire project and started with a fresh copy. Starting with a fresh copy, the problem came back.
Another note: Please make sure that your Flex compiler setting is accurate, in my case it's: --services=C:\CFusionMX7\wwwroot\WEB-INF\flex\flex-services.xml
Good luck and see if this works for you.
Cheers Ben for the Example.
I'm very very new to Flash and we are using ColdFusion but are not using Flex Data Services. I've been trying to figure out how to populate a combobox from a database and I'm just not having any luck.
My project is called "PreTraffic". I have my main file as "JobSearch.mxml" and a folder under the root named "cfc" with a file called "job.cfc".
job.cfc contains the following code:
<cfcomponent>
<!--- Get jobs --->
<cffunction name="GetJob" access="remote" returntype="query" output="false">
<cfset var job="">
<cfset var results="">
<cfquery datasource="discsdev" name="job">
SELECT job_id, job_title
FROM job
WHERE status = 'O'
ORDER BY job_title
</cfquery>
<cfquery dbtype="query" name="results">
SELECT job_title AS label, job_id AS data
FROM job
ORDER BY label
</cfquery>
<cfreturn results>
</cffunction>
</cfcomponent>
And JobSearch.mxml has the following code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns="*"
layout="absolute"
backgroundGradientColors="[#ffffff, #d0d0d0]"
creationComplete="InitApp()">
<mx:Style source="style.css" />
<mx:Script>
<![CDATA[
public function InitApp():void {
jobSvc.GetJob();
}
]]>
</mx:Script>
<!-- ColdFusion CFC (via AMF) -->
<mx:RemoteObject id="jobSvc" destination="PreTraffic" showBusyCursor="true" />
<mx:VBox label="Job History" width="100%" height="100%" x="10" y="92">
<mx:Label text="Search jobs by"/>
<mx:Form label="Task" width="100%">
<mx:FormItem label="Job Name:">
<mx:ComboBox id="jobNameCB" dataProvider="{jobSvc.GetJob.results}"></mx:ComboBox>
</mx:FormItem>
</mx:Form>
<mx:HBox>
<mx:Button label="Search"/>
<mx:Button label="Clear"/>
</mx:HBox>
</mx:VBox>
</mx:Application>
My Compiler thingy points to:
-services "/Volumes/flexwwwroot/WEB-INF/flex/job-services-config.xml" -locale en_US
and job-services-config.xml contains the following code:
<destination id="PreTraffic">
<channels>
<channel ref="my-cfamf"/>
</channels>
<properties>
<source>flex.pretraffic.cfc.job</source>
<lowercase-keys>true</lowercase-keys>
</properties>
</destination>
Well, when I run the app... the combobox is not populated... Can anyone help with what I've done wrong?
Thanks!
April
Im having a similar problem, whats happening for me is that if I try to specify the dataprovider for my mx:ComboBox as "mySvc.getArtists.result", i receive a runtime error telling me that 'property result not found...'
Ive noticed that Datagrids populate nicely when returning multiple colimns from a query, but mx:Combo Box and mx:List dont work for me.
Im sure it has something to do with ArrayCollection being returned as opposed to Array, but not sure how to get results to display in these 2 types of elements