ColdFusion Architecture For RIAs - Part 4
We'll begin by talking about what changed on the client. For the most part, the code is the same. One change is that I'm now getting my data from a DataService tag (rather than a RemoteObject tag) - this tells Flex to talk to my LCDS data service to get data. I disabled autocommit in the DataService - if I didn't, edits to grid rows would be automatically committed on the server for me with no additional work on my part. I turned it off in order to show how you can manually handle commits. Beneath the grid are buttons to commit or rollback all of the changes you make in the grid - each button simply calls a method on the dataservice (appropriately named "commit()" and "revertChanges()"). That's right - commit/rollback functionality without writing any actual code - nice!. What's nicer is that now I can open multiple clients and browse the application, and changes made by one client immediately reflect in the grid for all other clients, and if I try to edit a grid row that another client edits at the same time, it handles that conflict for me, too - no additional client code required. Wow... thank you, DataServices! There are two other small changes in the client code. First, to initially populate the grid using a DataService, you actually tell the dataservice to "fill()" the ArrayCollection you want to populate (again - this is simpler than handling a Remote Object result event). The other change worth noting is that I added the [Managed] MetaData tag to the DTO - this tells Flex that LCDS is managing these objects on the server for me (yes, you still also leave in the [RemoteClass] directive for mapping data types, too). OK, so the client is even more robust, and was made so with less effort. Surely, this must mean I did more work on the server? Let's see.
The 4 ColdFusion Components from the first version of the application are all still intact in the LCDS version - and none of the 4 were changed in any way yet again. Again, I have added a 5th CFC, 'facade.cfc'... only my facade cfc contains a lot more code than the facade for my 'simple' Flex version. This is because in order for LCDS to expose all of that cool functionality to Flex - the ability to retrieve all of the objects, add/edit objects, handle conflicts, paginate, etc. - you must in turn expose this same functionality to LCDS from your server side application (my facade.cfc in this case). The two questions you may be asking are "What methods do you have to expose to LCDS?" and "How does LCDS know what methods to call and what to pass them?".
First, LCDS. LiveCycle Data Services works by passing requests to a user defined "destination" - remoting in CF works this way, too. A default CF installation has a "ColdFusion" destination defined in it's remoting services XML file which points at the CF Server root (so you can call any CFCs on the server) - in the case of LCDS we add a destination to our data-management-config.xml file. The LCDS destination contains a lot more information than a typical remoting destination, though. If you're using CF as the data provider, you want to specify the "coldfusion-dao" adapter for your destination (make sure you've enabled it first) and define the communication channels - for real time it's "cf-dataservice-rtmp" and for polling it's "cf-polling-amf". You then specify the component that the destination will use - in my case it's the full package name to my facade CFC. You have to specify, in a 'metadata/identity' node of the destination, the name of the property in your CFCs that is the unique identifier - this is used by LCDS for edits, conflict handling, etc. You also have a few properties you can define for the fill method (the method that gets all records) and there are other properties of the destination you can define, but for the most part, that's all you have to do. When you write Java code to talk to LCDS, in the destination you can specify the names of all the various methods to call (as well as some properties of those methods) but in the case of CF, LCDS is hard-wired to use reserve method names on the CFC you specified. So there's really very little to do to configure LCDS for CF. All the "work", as I suggested earlier, is in the component you point your destination at... so let's take a look at what I did in facade.cfc.
As I said, all of the original CFCs were left in tact - we need only expose the right functionality and data to LCDS from one CFC (facade) using reserve method names - that facade (often refered to as an Assembler) talks to my other CFCs. One thing to note is that LCDS will pass a change object (or an array of change objects) to many of these methods. That change object has methods to tell you if this is an add, edit, or delete, and has methods for getting the old data and the new data (the old and new DTOs). The methods LCDS expects and a brief description of each are as follows:
- fill
- Returns all entries as an array of DTOs
- get
- Gets one DTO - receives a structure containing the unique id of the DTO to return
- sync
- Sync accepts an array of change objects - for each one, the sync method passes the change object to the appropriate action method (add/edit/delete method). Those 'action' methods return the new DTO (or old if there's a conflict or some business rule failure) which is placed in an array that the sync method ultimately returns.
- count
- Tells LCDS how many DTOs there are.
- create
- Receives a change object, creates the new DTO, adds that to the newVersion property of the change object, marks the change object as 'processed', and then returns the change object
- update
- Receives a change object and compares it's old DTO with the matching live DTO to make sure there isn't a conflict (resulting from trying to edit a DTO that another client already edited) - if there is, it flags the conflict in the change object. If there isn't, it commits, adds the new DTO to the change object, and returns it.
- delete
- Receives a change object and checks to see if that DTO was already deleted (in which case it flags the change object as 'failed'). If it wasnt, the method performs the delete, adds the deleted ID to the change object to flag it as deleted to all other clients, and marks the change object as processed.
All of this might seem like a lot of work, but if you download the code (http://www.horwith.com/downloads/abook-lcds.zip) and take a look at the facade, you'll see that it's actually fairly simple to implement - especially given the functionality it allows you to drop-in to your Flex/LCDS applications. That zip file also includes the Flex LCDS code, and the config file for my LCDS server destination. If you're comfortable working with CF, using it as a provider to LCDS allows you to build very rich clients in Flex without having to struggle with client code. You could, of course, use this facade to introduce many of the same features to "regular" CF applications as well.
I hope these past 4 blog entries were useful for folks. Here are the links to the various versions of the code:
Regular CF Version
CF AJAX Version
Flex Version
LCDS Version

Flex and especially LCDS are pretty new to me but I googled LCDS changeObject and this blog entry was first up.
I haven't delved into the code yet, but I just wondered if it will help me get to a grid with Undo/Redo buttons that give a user that sort of intuitive feature you get on a spreadsheet app. Or am I barking up the wrong tree?
Thanks
Not sure if you can help, but despite the flex version working fine I'm having no luck with the LCDS version.
FlexBuilder gives me an error
channel not found for reference 'cf-dataservice-rtmp' in destination 'cfabook'.
I've tried replacing my data-management-config.xml with yours, and I've also tried adding a channel to the services-config.xml but I just end up breaking the installation it seems.
Any idea where I'm going wrong?
Thanks
Brian
Destination 'cfabook' either does not exist or the destination has no channels defined (and the application does not define any default channels.
from the browser, even though I've copied in your data-management-config.xml into the web-inf/flex directory.
Very frustrating
However I now get the following error:
TypeError: Error #1009: Cannot access a property or method of a null object reference.
at main/filterIt()[D:\ColdFusion8\wwwroot\abook-lcds\src\main.mxml:70]
I can't see the commit or rollback buttons you mention in your blog, just the following
<mx:Button label="Add Entry" click="addEntry()" id="addbtn" left="202" bottom="100"/>
<mx:Button label="Test" click="testIt()" left="419" bottom="100" id="testbtn"/>
Have I got the right code?
Thanks
Brian
Are you sure you're looking at the code from the LCDS sample? You're in the right file if there's an <mx:DataService> tag in it...
This is the code from the end of the mxscript to the end of the application...
</mx:Script>
<mx:DataService id="cfService" destination="cfabook" />
<mx:TraceTarget />
<mx:ApplicationControlBar width="100%" x="240" y="10" id="navBar" dock="true" horizontalAlign="center" />
<mx:DataGrid dataProvider="{entriesArray}" left="78" right="62" top="194" bottom="174" id="abookDG" editable="true">
<mx:columns>
<mx:DataGridColumn headerText="First Name" dataField="firstName"/>
<mx:DataGridColumn headerText="Last Name" dataField="lastName"/>
<mx:DataGridColumn headerText="Email Address" dataField="email"/>
</mx:columns>
</mx:DataGrid>
<mx:Button label="Add Entry" click="addEntry()" id="addbtn" left="202" bottom="100"/>
<mx:Button label="Test" click="testIt()" left="419" bottom="100" id="testbtn"/>
</mx:Application>
Is that the right one?
I apologize - looks like the wrong code got zipped. I've replaced the zip file with a new one.... try redownloading from http://www.horwith.com/downloads/abook-lcds.zip again!
No obvious error this time, I do have 2 warnings though, both saying
1008: variable 'objEdit' has no type declaration. abook-flex/src main.mxml line 62 1242822995171 86
When the app launches there is no data, after about 20 seconds the message appears
An error occured while committing your data
The flex version works fine, so the datasource and database should be correct.
FlexBuilder debugger gives me the following, but I don't know where to start trying to fix it
[SWF] /abook-lcds/bin-debug/main.swf - 1,395,416 bytes after decompression
'cds-consumer-cfabook-null' consumer set destination to 'cfabook'.
Configuration for destination='cfabook':
<properties>
<metadata>
<identity property="entryID"/>
</metadata>
</properties>
New DataService for destination: cfabook
'13F24795-8539-F2F9-FD86-627A206EA0AB' producer set destination to 'cfabook'.
Adding data service: cfabook to the data store: null initialized: false
DataService.fill() called for destination: cfabook with args: []
data store: null is initialized
'cf-dataservice-rtmp' channel endpoint set to rtmp://localhost:2048
'cf-dataservice-rtmp' channel settings are:
<channel id="cf-dataservice-rtmp" type="mx.messaging.channels.RTMPChannel">
<endpoint uri="rtmp://{server.name}:2048"/>
<properties/>
</channel>
'ds-producer-cfabook' producer sending message '6ECF1964-A37D-B308-26FB-627A25AEEC26'
'cf-dataservice-rtmp' channel got connect attempt status. (Object)#0
code = "NetConnection.Connect.Success"
description = "Connection succeeded."
details = (null)
id = "816C3EED-670B-E965-8BFC-5506F5CB7E66"
level = "status"
objectEncoding = 3
'cf-dataservice-rtmp' channel is connected.
'cf-dataservice-rtmp' channel sending message:
(mx.messaging.messages::CommandMessage)
body=(Object)#0
clientId=(null)
correlationId=""
destination="cfabook"
headers=(Object)#0
messageId="0A267C56-3002-13E2-0395-627A2B5B0C66"
operation="client ping"
timeToLive=0
timestamp=0
'cf-dataservice-rtmp' channel sending message:
(mx.data.messages.DataMessage)
messageId = '6ECF1964-A37D-B308-26FB-627A25AEEC26'
operation = fill
destination = cfabook identity = (null)
body = []
headers = {}
'ds-producer-cfabook' producer connected.
'ds-producer-cfabook' producer acknowledge of '0A267C56-3002-13E2-0395-627A2B5B0C66'.
'ds-producer-cfabook' producer acknowledge of '6ECF1964-A37D-B308-26FB-627A25AEEC26'.
'ds-producer-cfabook' producer fault for '6ECF1964-A37D-B308-26FB-627A25AEEC26'.
Wish it told me what the produce fault was. Any ideas?
Creating a test CFM that tries talking to the CFCs would be another quick way to do it - try running something like this:
<cfset foo = createObject('component', 'lcds-service.tmp.facade')>
<cfdump var="#foo.fill()#">
I tried putting a tst.cfm test file in the lcds-service directory. I changed tmp to cfc so the 2 lines of code became:
<cfset foo = createObject('component', 'lcds-service.cfc.facade')>
<cfdump var="#foo.fill()#">
when I run this I get the following
Element AENTRIES is undefined in VARIABLES.
The error occurred in D:\ColdFusion8\wwwroot\lcds-service\cfc\addressbook.cfc: line 47
Called from D:\ColdFusion8\wwwroot\lcds-service\cfc\facade.cfc: line 12
Called from D:\ColdFusion8\wwwroot\lcds-service\tst.cfm: line 2
45 :
46 : if (arguments.filter is ''){
47 : aEntry = variables.aEntries;
48 : } else {
49 : filterLen = len(arguments.filter);
Ben Nadel sent me your way when I had questions on LCDS-he said your the man to ask. [My original question to Ben is similar to my question I'm asking here after running thru your series.] I'm fairly new to Flex & LCDS, but have created a few successful trial apps.
I appreciate (this) your series on CF arch for RIAs. I've run thru all 4 versions & got all to work w/minor changes in XML & CFCs to reflect my environment diffs.
[My question is: How when making changes on CF server to auto-sync (reflect) those changes to Flex/LCDS clients?] So using your current code samples: How would you change your original CF version code to force an auto-sync (push) to all Flex/LCDS clients. It seems like you've put all the pieces out there, but I don't know what & how to call the correct facade.cfc funcs out of the CF code to facilitate. Please advise.
Thanks in advance,
Tom
The autoMerge property of the mx:DataService determines whether or not changes pushed from the server to the client will automatically be reflected in the client. If it's set to false, you'd need to manually call the merge() method of the Dataservice - the Dataservice mergeRequired property tells you if the server has sent any changes that require merging.
On the server, in your destination definition (in data-management-config.xml) you'll want to make sure the "auto-refresh" node (in the properties/server/fill-method node) is set to true and, for real time, make sure that in the destination "channels" block "cf-dataservice-rtmp" is the first (or only) channel.
Pretty much all of these properties and XML nodes default to what you need for real time... and it generally just works "like magic" :)
P.S. The best torrents search engine.
http://www.queentorrent.com
http://www.btscene.com/
http://www.realtorrentz.com
http://www.knight4noah.com http://www.fasteveisk.com Eve isk
http://www.nflcowboysjerseys.com/miles-austin-jers... Miles Austin Jersey
http://www.nflcowboysjerseys.com/marion-barber-jer... Marion Barber Jersey
http://www.nflcowboysjerseys.com/tony-romo-jerseys... Tony Romo Jersey
http://www.nflcowboysjerseys.com/dez-bryant-jersey... Dez Bryant Jersey
http://www.nflcowboysjerseys.com/adam-jones-jersey... Adam Jones Jersey
http://www.nflcowboysjerseys.com/bill-bates-jersey... Bill Bates Jersey
http://www.nflcowboysjerseys.com/demarcus-ware-jer... DeMarcus Ware Jersey