Related Article(s): AjaxCFC ported to ExtJS
My integration of ExtJS, ajaxCFC and ColdFusion continues.
Note: This is for version 3.3.1. I’ve also been happily playing around with version 4, but until it’s officially released, I must continue building with 3.3.1.
Anyway, here’s what I was trying to solve. When I want to allow users to serach for items from a ComboBox, I configure a store, and inside the store, I configure an Ext.data.HttpProxy to hit a separate ColdFusion page that performs a query and returns the results.
var WidgetStore = new Ext.data.Store({
proxy: new Ext.data.HttpProxy({
url: 'liveQueries/widgets.cfm'
}),
reader: new Ext.data.JsonReader({
root: 'data',
totalProperty:'totalRecords'
}, Widget)
});
<cfsetting enablecfoutputonly="true">
<cfparam name="query" default="">
<cfquery name="chargeCodes" datasource="#getDatasource().getName()#">
SELECT UNIQUE widget_seq_no, identifier, descr
FROM widgets
WHERE (REGEXP_LIKE(identifier,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i')
OR REGEXP_LIKE(descr,<cfqueryparam cfsqltype="cf_sql_varchar" value="^#query#">,'i'))
AND widget_type_cd='SNAPPYDS'
ORDER BY identifier asc
</cfquery>
<cfscript>
jsonBean = createobject("component","ajaxCFC.JSON");
jsonEncodedCriteria = jsonBean.encode(data=chargeCodes, queryFormat="array");
writeOutput(jsonEncodedCriteria);
</coldfusion>
<cfsetting enablecfoutputonly="false">
I hate this, because I already am using my Ext.AjaxCFC class to query my ColdSpring beans asynchronously, so why do I have to set up these standalone ColdFusion pages to performs queries that should be executed in the component?
I decided to take matters into my own hands, and I have to give the ExtJS team props again, because it was easy to accomplish by extending the Ext.data.MemoryProxy class. As you can see, all I need to do is override the doRequest() method to execute Ext.AjaxCFC.request() with the data passed in during initialization.
Ext.ns('Ext.ux.data');
Ext.ux.data.AjaxCFCProxy = Ext.extend(Ext.data.MemoryProxy, {
constructor : function(data){
Ext.ux.data.AjaxCFCProxy.superclass.constructor.call(this);
this.data = data;
},
doRequest : function(action, rs, params, reader, callback, scope, arg){
this.data.data.query = params.query;
Ext.AjaxCFC.request({
bean : this.data.bean,
method : this.data.method,
data : this.data.data,
success: function(rs) {
var result = reader.readRecords(rs);
callback.call(scope, result, arg, true);
},
error: function(results) {
Ext.MessageBox.alert('Load Failed', 'Unable to load requested data');
}
});
}
});
WidgetSearch = new Ext.form.ComboBox({
minChars: 2,
loadingText: '',
itemSelector: 'div.search-item',
triggerClass: 'x-form-search-trigger',
emptyText: '',
width: 300,
listWidth: 0,
store: new Ext.data.Store({
// I've extended the base Ext.data.MemoryProxy()
// class to use the AjaxCFC object for searching
// instead of having to use a liveQuery. Much cleaner.
proxy: new Ext.ux.data.AjaxCFCProxy({
bean : 'Widget',
method : 'search',
data : { source_table : 'WIDGET' }
}),
It’s somewhat difficult to find examples of the autocomplete ComboBox that the Ext library provides, so I’ll add another one to the mix in the hopes that it makes it easier for future implementers to find.
First, let’s look at the code you need. The Ext stylesheet and the ext-all.js library. Then you’ll need your own, custom interaction code. My naming convention is to start with interaction and then the page to which the code applies.
<link href="css/ext-all.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="js/ext-all.js"></script>
<script type="text/javascript" src="js/interaction.example.js"></script>
This article is going to focus on the HTTPProxy code for the autocomplete feature. The one argument you need is URL, and it value will be the name of the file that is actually going to perform the query and return the results. This code is simply creating a connection to a page that will be used when the user types in a search string.
In the example I’m pulling from, I’m searching against a list of facilities for the company.
facilityProxy = new Ext.data.HttpProxy({url: 'liveQueries/facilities.cfm'});
When the user types in a search string, Ext will then use the HTTPProxy to call facilities.cfm with a URL variable named query that contains the search string. Therefore, if the user typed in ‘PHIL’, the proxy URL would be liveQueries/facilities.cfm?query=PHIL.
Now let’s look at the facilities.cfm code. First, we have to capture the query variable being passed to the page by Ext, which can be done simply with a <cfparam> tag. Then we execute our query. Once we have the resultset, we’ll need to serialize it. I like JSON serialization, so I used the CFJSON code from Thomas Messier, Jehiah Czebotar, and others.
<cfsetting enablecfoutputonly="true">
<cfparam name="query" default="">
<cfquery name="facilities" datasource="#datasource_name#">
select unique facility_no, facility_legal_name
from chg_facility
where (REGEXP_LIKE(facility_no,'#query#','i') OR REGEXP_LIKE(facility_legal_name,'#query#','i'))
order by facility_no asc
</cfquery>
<cfscript>
jsonBean = createobject("component","webapps.charm.model.ajax.JSON");
jsonEncodedCriteria = jsonBean.encode(data=facilities, queryFormat="array");
writeOutput(jsonEncodedCriteria);
</cfscript>
<cfsetting enablecfoutputonly="false">
Ok, so now we’ve got a JSON-serialized query. What do we do with it? Well, Ext just happens to have a built-in JSON reader. Just create a new JsonReader object, tell it what node contains the data (in our case, the node name is data) and optionally provide a totalProperty argument that contains the total number of records returned.
You then provide a defintion of what a single record of data consist. You can define a seperate object called a Record….
facilityRecord = Ext.data.Record.create([
{name: 'facility_no', type: 'string'},
{name: 'facility_legal_name', type: 'string'}
]);
facilityReader = new Ext.data.JsonReader({
root: "data",
totalProperty: "recordcount"
}, facilityRecord);
Or just do it inline if the record is simple enough.
facilityReader = new Ext.data.JsonReader({
root: "data",
totalProperty: "recordcount"
}, [
{name: 'facility_no', type: 'string'},
{name: 'facility_legal_name', type: 'string'}
]);
Alright, so we’ve got a proxy object to facilities.cfm that will perform the query on the user’s search string and return JSON-serialized data. We’ve defined the structure of each record, and use a built-in JSON reader to parse the results.
Lastly, we need to populate a data Store with the deserialized data set that we’ve retrieved. We simply provide it with the name of the proxy we’ll be using and which reader it should use to deserialize the data.
facilityStore = new Ext.data.Store({
proxy: facilityProxy,
reader: facilityReader
});
You can also define each element inline instead of creating a separate variables for each object. Here’s an example:
new Ext.data.Store({
proxy: new Ext.data.HttpProxy({url: 'liveQueries/facilities.cfm'}),
reader: new Ext.data.JsonReader({
root: "data",
totalProperty: "recordcount"
}, [
{name: 'facility_no', type: 'string'},
{name: 'facility_legal_name', type: 'string'}
])
})
Now that’s we’ve got some interaction code running, let’s start creating the actual ComboBox. Create a simple HTML file and place an input element on the page with a unique name.
<input type="text" size="20" id="facilitySearchField">
Then, back in your interaction code, let’s create a ComboBox instance and tell it to use the data store that we’ve already defined.
var search = new Ext.form.ComboBox({
store: facilityStore,
minChars:2,
itemSelector: 'div.search-item',
tpl: new Ext.XTemplate(
'<tpl for="."><div class="search-item">',
'{facility_no} - {facility_legal_name}',
'</div></tpl>'
),
onSelect: function(record){
// What you want to happen when the enter selects a record (or hit the ENTER key)
// Example (redirect to another page):
// document.location.href = 'showFacilityDetails.cfm?facilitySelected&fid=' + record.data.facility_no;
}
});
// Apply the comboBox to the <input> element in our HTML page.
search.applyTo('facilitySearchField');
<html>
<head>
<link href="css/ext-all.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="js/ext-all.js"></script>
<script type="text/javascript" src="js/interaction.example.js"></script>
</head>
<body>
<input type="text" size="20" id="facilitySearchField">
</body>
</html>
facilityProxy = new Ext.data.HttpProxy({url: 'liveQueries/facilities.cfm'});
facilityRecord = Ext.data.Record.create([
{name: 'facility_no', type: 'string'},
{name: 'facility_legal_name', type: 'string'}
]);
facilityReader = new Ext.data.JsonReader({
root: "data",
totalProperty: "recordcount"
}, facilityRecord);
facilityStore = new Ext.data.Store({
proxy: facilityProxy,
reader: facilityReader
});
var search = new Ext.form.ComboBox({
store: facilityStore,
minChars:2,
itemSelector: 'div.search-item',
tpl: new Ext.XTemplate(
'<tpl for="."><div class="search-item">',
'{facility_no} - {facility_legal_name}',
'</div></tpl>'
),
onSelect: function(record){ }
});
search.applyTo('facilitySearchField');
<cfsetting enablecfoutputonly="true">
<cfparam name="query" default="">
<cfquery name="facilities" datasource="#datasource_name#">
// Perform search based on user's search string (query parameter)
</cfquery>
<cfscript>
jsonBean = createobject("component","webapps.model.ajax.JSON");
jsonEncodedCriteria = jsonBean.encode(data=facilities, queryFormat="array");
writeOutput(jsonEncodedCriteria);
</cfscript>
<cfsetting enablecfoutputonly="false">