I’ve been using the AjaxCFC library for years. It’s my preferred way of integrating Javascript and ColdFusion via AJAX. I’ve even modified it from its original form so that my implementation was strictly for integration with jQuery, only returns JSON strings (ignoring WDDX and simple string), and can work with ColdSpring.
Now that I’m a heavy user of the Sencha ExtJS framework, I thought it would be useful to port the jQuery.ajaxCFC.js file over to an Ext.AjaxCFC.js file that extended the native Ext.data.Connection class and utilized the Ext.Ajax object.
Took me about half the day, but I finally got a working Ext.AjaxCFC.request() method that uses the same syntax as the $.AjaxCFC() method. For those familiar with the inner workings and code of the jQuery AjaxCFC class, this will look very familiar.
So now I can make AJAX calls using native ExtJS classes, access ColdSpring beans in my application’s bean factory, or connect directly to any CFC
Ext.AjaxCFCConnection = Ext.extend(Ext.data.Connection, {
data : null,
queryFormat : 'array',
factory : (typeof(__ajaxConfig) == 'undefined') ? null : __ajaxConfig.beanFactory,
timeout : (typeof(__ajaxConfig) == 'undefined') ? 30000 : __ajaxConfig.defaultTimeout,
url : __ajaxConfig.url,
bean : null,
request : function(arguments) {
var params = (typeof(arguments.data) == 'undefined') ? {} : arguments.data;
arguments.params = {};
arguments.params['C0-ID'] = (Math.floor(Math.random() * 10001) + "_" + new Date().getTime()).toString(),
arguments.params['method'] = 'init';
arguments.params['component'] = arguments.component;
arguments.params['bean'] = (typeof(arguments.bean) == 'undefined') ? this.bean : arguments.bean;
arguments.params['factory'] = this.factory;
arguments.params['C0-METHODNAME'] = arguments.method;
arguments.params['queryFormat'] = (typeof(arguments.queryFormat) == 'undefined') ? this.queryFormat : arguments.queryFormat;
arguments.params['C0-PARAM0'] = params;
arguments.url = this.url + '?method=' + arguments.params['method'];
arguments.method = 'POST';
arguments.failure = arguments.error;
arguments.timeout = this.timeout;
var ____success = arguments.success;
arguments.success = function(data) {
data = data.responseText.replace(/^\s*|\s*$/g, '');
if (data.substring(0,9) == '__json__:') {
data = Ext.util.JSON.decode(data.slice(9));
}
____success(data, this);
};
if ( params ) {
if (typeof params != 'string') {
arguments.params['C0-PARAM0'] = Ext.util.JSON.encode(params);
}
}
Ext.Ajax.request(arguments);
}
});
Ext.AjaxCFC = new Ext.AjaxCFCConnection();
// Include Ext.AjaxCFC code
<script type="text/javascript" src="js/Ext.AjaxCFC.js"></script>
// Default configuration properties for the ajaxCFC library
__ajaxConfig = {
'url':'/myApp/ajaxCFC/ajax.cfc',
'defaultTimeout':30000,
'beanFactory':'application.beanFactory'
};
// AjaxCFC call using Ext.data.Connection class
Ext.AjaxCFC.request({
bean: 'AColdSpringBean',
method: 'aMethod',
data: {
'id': 416198,
'first_name': 'Steve',
'last_name': 'Brownlee'
},
success: function(details, s){
DataStore.loadData(details);
},
error: function(results){
Ext.MessageBox.alert('Search Failed', 'An unexpected error occurred. Please try again.');
}
});
Otherwise known as a transfer object is simply a design pattern for exchanging data between disparate systems. I’m working on a application with a Flex UI and a ColdFusion backend, and I initially discovered that if I return a query directly to Flex, it is converted into an ArrayCollection populated with simple Objects.
What I wanted was an array of specifically typed objects that I could cast as value objects in Flex. Here’s a very simplistic example of an Employee value object in Flex. It has two properties:
package net.fusioncube.transferObjects
{
[RemoteClass(alias="net.fusioncube.model.employee.Employee")]
[Bindable]
public class Employee
{
private var _employeeID:uint;
private var _employeeName:String;
/* constructor */
public function Effort(employee_id:uint = 0,
employee_name:String = "")
{
this._employeeID= employee_id;
this._employeeName= employee_name;
}
public function get employee_id():uint
{
return _employeeID;
}
public function set employee_id(value:uint):void
{
if(value != 0) _employeeID= value;
}
public function get employee_name():String
{
return _employeeName;
}
public function set employee_name(value:String):void
{
if(value != "") _employeeName= value;
}
}
}
You may have noticed the RemoteClass metadata applied to the Actionscript class. What this does is ensure that this Flex class maps directly to a ColdFusion component value object. To make a ColdFusion component value object is simple: you use the <cfproperty> tag for each property and specify each one’s type.
In this example, I’ve also added an init() method, whose use we’ll discover later.
<cfcomponent displayname="Employee" output="false">
<cfproperty name="employee_id" type="numeric" default="" />
<cfproperty name="employee_name" type="string" default="" />
<cffunction name="init" access="public" returntype="net.fusioncube.model.employee.Employee" output="false">
<cfargument name="employee_id" type="numeric" required="false" />
<cfargument name="employee_name" type="string" required="false" />
<cfset this['effort_id'] = arguments.effort_id />
<cfset this['employee_name'] = arguments.employee_name />
<cfreturn this />
</cffunction>
</cfcomponent>
Again, as a very simple example, let’s say I have an EmployeeService component in my service layer and have a method listEmployees() that simply queries the employees table and returns all rows.
<cffunction name="listEmployees" access="public" output="false" returntype="query">
<cfset var effortQuery = "" />
<cfquery datasource="#variables.config.getSetting("datasource")#" name="employeeQuery">
select e.* from employees e
</cfquery>
<cfreturn effortQuery />
</cffunction>
Since I don’t want to return a query, and I certainly don’t want to have two methods in every single component – one that returns a query and one that returns an array of objects – I use the handy, dandy AOP feature in ColdSpring to apply an advice that does it at runtime.
In this advice, I simply loop over the query’s rows and columns to create a simple structure containing the key/value pairs for each row. It then creates a new component for each query row by passing that structure to the init() function – which you saw above – and adds it to an array.
The result is an array of Employee-typed objects.
<cfcomponent displayname="QueryToVOAdvice" extends="coldspring.aop.MethodInterceptor">
<cffunction name="init" returntype="utility.aop.advice.converters.QueryToVOAdvice" access="public" output="false">
<cfreturn this />
</cffunction>
<cffunction name="invokeMethod" access="public" returntype="any">
<cfargument name="methodInvocation" type="coldspring.aop.MethodInvocation" required="false" />
<cfscript>
var local = structNew();
local.queryRow = 1;
// Create an array to hold the value objects
local.valueObjectArray = ArrayNew(1);
// Store the resulting query object from the method execution
local.queryObject = arguments.methodInvocation.proceed();
// Loop over the query rows
for(local.queryRow=1; local.queryRow lte local.queryObject.recordCount; local.queryRow = local.queryRow+1)
{
local.voStruct = structNew();
// Loop over the query columns and create a key/value pair for each value
// in this row of the query
for(local.columnNum=1; local.columnNum lte listLen(local.queryObject.columnList); local.columnNum = local.columnNum+1)
{
local.columnName = listGetAt(local.queryObject.columnList, local.columnNum);
local.voStruct[local.columnName] = local.queryObject[local.columnName][local.queryRow];
}
// Create an instance of the value object (obtained from the getValueObject method)
// initializing it with the structure we created. Add the value object to the array.
arrayAppend(local.valueObjectArray, createObject('component', arguments.methodInvocation.getTarget().getValueObject()).init(argumentCollection=local.voStruct));
}
return local.valueObjectArray;
</cfscript>
</cffunction>
</cfcomponent>
For each of my beans, in this case the Employee bean, I can simply apply an advice in the definition. For this to work, I create a valueObject property in each component.
<component name="Employee">
<cffunction name="setValueObject" access="public" output="false" returntype="void">
<cfargument name="valueObject" type="string" required="true" />
<cfset variables.valueObject = arguments.valueObject />
</cffunction>
<cffunction name="getValueObject" access="public" output="false" returntype="string">
<cfreturn variables.valueObject />
</cffunction>
</component>
Now when I define my Employee bean, I specify the type of the value object (the type that gets returned from the init() function), and then apply the advisor (shown below).
<bean id="Employee" class="coldspring.aop.framework.ProxyFactoryBean">
<property name="target">
<bean class="net.fusioncube.model.employee.EmployeeService">
<property name="valueObject">
<value>net.fusioncube.model.employee.Employee</value>
</property>
</bean>
</property>
<property name="interceptorNames">
<list>
<value>queryToValueObjectAdvisor</value>
</list>
</property>
</bean>
<bean id="queryToValueObjectAdvisor" class="coldspring.aop.support.RegexMethodPointcutAdvisor">
<property name="advice">
<bean class="utility.aop.advice.converters.QueryToVOAdvice"/>
</property>
<property name="pattern">
<value>^list.*$</value>
</property>
</bean>
Here’s an image of an example array I get back from ColdFusion in one of my current apps. You can see that each object was automatically converted into a Teammate (equivalent of an Employee) rather than a simple Object.
Caveat: If you’re passing back queries with a thousand or more rows, and are running CFMX 7 or older, then this solution may cause serious performance problems as object creation is notoriously “slow” in those versions of ColdFusion.