Well we're still a ways from havinga working prototype but I thought I'd post some code to give people an idea of where I'm heading (and to get Mark off my back ;p). Much thanks go Justin McLean and Robin Hilliard for answering stupid questions.
here is the current Bender.as:
package com.ls.bender {
public class Bender {
import mx.rpc.events.ResultEvent;
import mx.rpc.events.FaultEvent;
import mx.rpc.remoting.RemoteObject;
private var Remote : RemoteObject = new RemoteObject();
private var RequestQueue : Object = new Object();
public function Bender() {
Remote.destination = "ColdFusion";
Remote.source = "com.ls.bender.Bender";
}
public function read(obj : *, classname : String, key : String) : * {
if (obj.getONDATA()) {
Remote.getOperation("read").addEventListener(ResultEvent.RESULT, readResultHandler);
}
if (obj.getONFAULT()) {
Remote.getOperation("read").addEventListener(FaultEvent.FAULT, readFaultHandler);
}
RequestQueue[Remote.read(classname, key)] = obj;
}
private function readResultHandler(event : ResultEvent) : void {
RequestQueue[event.token].fill(event);
RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
}
private function readFaultHandler(event : FaultEvent) : void {
RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
}
public function save(obj : Object) : void {
if (obj.getONDATA()) {
Remote.getOperation("save").addEventListener(ResultEvent.RESULT, saveResultHandler);
}
if (obj.getONFAULT()) {
Remote.getOperation("save").addEventListener(FaultEvent.FAULT, saveFaultHandler);
}
var args : Object = new Object();
args.OBJECT = obj.data();
RequestQueue[Remote.save(args)] = obj;
}
private function saveResultHandler(event : ResultEvent) : void {
RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
}
private function saveFaultHandler(event : FaultEvent) : void {
RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
}
public function del(obj : Object) : void {
if (obj.getONDATA()) {
Remote.getOperation("del").addEventListener(ResultEvent.RESULT, delResultHandler);
}
if (obj.getONFAULT()) {
Remote.getOperation("del").addEventListener(FaultEvent.FAULT, delFaultHandler);
}
var args : Object = new Object();
args.OBJECT = obj.data();
RequestQueue[Remote.del(args)] = obj;
}
private function delResultHandler(event : ResultEvent) : void {
RequestQueue[event.token].getONDATA()(RequestQueue[event.token]);
}
private function delFaultHandler(event : FaultEvent) : void {
RequestQueue[event.token].getONFAULT()(RequestQueue[event.token]);
}
}
}
Here is Bender.cfc:
<!--- $ID $
author : Toby Tremayne (
toby@lyricist.com.au)
written: 2007-04-08
"
description: Creates a bridge between standard Transfer CRUD methods and actionscript objects
Copyright 2007 Lyricist Software
Unless required by applicable law or agreed to in writing, this software
distributed on an "AS IS
" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied.
All rights to this code belong to Lyricist Software
$Header $
--->
<cfcomponent name="Bender" displayName="Bender : Transfer-ActionScript Bridge" hint="serves as a bridging service between ActionScript and Transfer">
<cfset variables.instance = structNew() />
<cfset variables.instance.Transfer = createObject("component", "transfer.TransferFactory").init("Datasource.xml", "Transfer.xml", "/Transfer/resources/definitions").getTransfer() />
<cfif not isDefined("application")><cfapplication name="Bender" sessionManagement="true" /></cfif>
<cffunction name="init" access="public" returntype="com.ls.bender.Bender" hint="default constructor">
<cfreturn this />
</cffunction>
<cffunction name="get" access="remote" returntype="any" hint="">
<cfargument name="class" type="string" required="true" />
<cfargument name="key" type="any" required="true" />
<cfscript>
var oObj = instance.Transfer.get(arguments.class, arguments.key);
stTemp = oObj.getMemento();
stTemp.ASCLASSNAME = "com.ls.bender.transferobjects.#listLast(replace(oObj.getClassName(),
"""",
"",
"all"),
".")#
";
return stTemp;
</cfscript>
</cffunction>
<cffunction name="save" access="remote" returntype="void" hint="">
<cfargument name="object" type="any" required="true" />
<cfscript>
var key = getObjectPrimaryKeyValue(arguments.object);
var oObj = instance.Transfer.get(arguments.object.className, key);
var omd = instance.Transfer.getTransferMetaData(arguments.object.className);
var iterator = omd.getPropertyIterator();
writeoutput(key);
// loop over the properties
while (iterator.hasNext()) {
name = iterator.next().getName();
writeoutput("oObj.set#Name#(
""#arguments.object[
"#name#"]#
"")
<br>");
// copy the data from the struct we received into the Transfer Object
evaluate("oObj.set#name#(
""#arguments.object[
"#name#"]#
"")
");
}
name = omd.getPrimaryKey().getName();
// set the primary key
evaluate("oObj.set#name#(
""#arguments.object[
"#name#"]#
"")
");
// save the changes
instance.Transfer.save(oObj);
</cfscript>
</cffunction>
<cffunction name="del" access="remote" returntype="void" hint="">
<cfargument name="object" type="any" required="true" />
<cfscript>
var key = getObjectPrimaryKeyValue(arguments.object);
var oObj = instance.Transfer.get(arguments.object.className, key);
instance.Transfer.delete(oObj);
</cfscript>
</cffunction>
<cffunction name="getObjectPrimaryKeyValue" access="private" returntype="any" hint="">
<cfargument name="object" type="any" required="true" />
<cfscript>
// get the TO metadata
var omd = instance.Transfer.getTransferMetaData(arguments.object.className);
if (isStruct(arguments.object)) {
return arguments.object[omd.getPrimaryKey().getName()];
} else {
// set the primary key
return evaluate("oObj.get#name#(
""#arguments.object[
"#name#"]#
"")");
}
</cfscript>
</cffunction>
</cfcomponent>and here is a sample autogenerated class for a "User" object (User.as):
package com.ls.bender.transferobjects {
public class User {
import mx.rpc.events.ResultEvent;
private var userUUID : String;
private var firstName : String;
private var lastName : String;
private var emailAddress : String;
private var password : String;
private var userToRole : Array;
private var TransferClassName : String =
"mglogin.User";
private var ASClassName : String =
"com.ls.bender.transferobjects.User";
private var onData : Function;
private var onFault : Function;
public function User() {
setUSERUUID(
"");
setFIRSTNAME(
"");
setLASTNAME(
"");
setEMAILADDRESS(
"blah@blah.com");
setPASSWORD(
"");
setUSERTOROLE(new Array);
}
public function setUSERUUID(userUUID : String) : void {
this.userUUID = userUUID;
}
public function getUSERUUID() : String {
return this.userUUID;
}
public function setFIRSTNAME(firstName : String) : void {
this.firstName = firstName;
}
public function getFIRSTNAME() : String {
return this.firstName;
}
public function setLASTNAME(lastName : String) : void {
this.lastName = lastName;
}
public function getLASTNAME() : String {
return this.lastName;
}
public function setEMAILADDRESS(emailAddress : String) : void {
this.emailAddress = emailAddress;
}
public function getEMAILADDRESS() : String {
return this.emailAddress;
}
public function setPASSWORD(password : String) : void {
this.password = password;
}
public function getPASSWORD() : String {
return this.password;
}
public function setUSERTOROLE(userToRole : Array) : void {
this.userToRole = userToRole;
}
public function getUSERTOROLE() : Array {
return this.userToRole;
}
public function getCLASSNAME() : String {
return this.TransferClassName;
}
public function getASCLASSNAME() : String {
return this.ASClassName;
}
public function setCLASSNAME(className : String) : void {
this.TransferClassName = className;
}
public function setASCLASSNAME(asclassname : String) : void {
this.ASClassName = asclassname;
}
public function setONDATA(func : Function) : void {
this.onData = func;
}
public function getONDATA() : Function {
return this.onData;
}
public function setONFAULT(func : Function) : void {
this.onFault = func;
}
public function getONFAULT() : Function {
return this.onFault;
}
public function fill(event : ResultEvent) : void {
this.setUSERUUID(event.result.USERUUID);
this.setFIRSTNAME(event.result.FIRSTNAME);
this.setLASTNAME(event.result.LASTNAME);
this.setEMAILADDRESS(event.result.EMAILADDRESS);
this.setPASSWORD(event.result.PASSWORD);
this.setUSERTOROLE(event.result.USERTOROLE);
this.setCLASSNAME(event.result.CLASSNAME);
this.setASCLASSNAME(event.result.ASCLASSNAME);
}
public function data() : Object {
var Obj : Object = new Object();
Obj.userUUID = getUSERUUID();
Obj.firstName = getFIRSTNAME();
Obj.lastName = getLASTNAME();
Obj.emailAddress = getEMAILADDRESS();
Obj.password = getPASSWORD();
Obj.userToRole = getUSERTOROLE();
Obj.ClassName = getCLASSNAME();
Obj.ASClassName = getASCLASSNAME();
return Obj;
}
}
}
Finally, here's an example of the use of Bender in mxml. I'm not doing anything clever here, just a simple get and a delete, but hopefully it gives you an idea.
<?xml version=
"1.0" encoding=
"utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import com.ls.bender.transferobjects.User;
import com.ls.bender.Bender;
private var oUser : User = new User();
private var bender : Bender = new Bender();
public function saveIt(e : Event) : void {
oUser.setONDATA(showResults);
bender.read(oUser,
"mglogin.User", 'CF47B443-FD21-C25E-910A87B22004637A');
oUser.setONDATA(showDeleted);
oUser.setONFAULT(showDeleteFault);
bender.del(oUser);
}
public function showDeleted(obj : *) : void {
result_text.text =
"Deleted";
}
public function showDeleteFault(obj : *) : void {
result_text.text =
"Delete Failed";
}
public function showResults(obj : *) : void {
result_text.text = obj.getEMAILADDRESS();
}
]]>
</mx:Script>
<mx:Panel title="RPC Test" paddingTop="20" paddingLeft="20" paddingBottom="20" paddingRight="20">
<mx:TextArea x="10" y="36" width="319" height="113" id="result_text"/>
<mx:Button x="250" y="157" label="saveObject" width="79" click="saveIt(event);"/>
</mx:Panel>
</mx:Application>All comments are welcome - it works with what you see here, but various things haven't been done yet. No list functions are in there, complex properties aren't being dealt with yet, mapping to custom cfcs is not done yet. Now that I have the basic concept working, I am going to expand the cfc next to handle validation returns and custom cfcs.
Feedback would be appreciated!