New mxAjax screencast - mxData

Arjun has added a screencast dealing with the mxAjax mxData tag:

http://www.techscreencast.com/screencast/mxajax_mxdata/mxajax_mxdata.html

mxAjax - closing a DomInclude from within the popup window

The mxAjax DomInclude (http://www.indiankey.com/mxajax/examples/mxDomInclude1.cfm) automatically provides a way to close the popup content (by clicking the link used to open it). However you might wish to have a link inside the popup that closes the DomInclude.

When you create the DomInclude, keep the created object:

function init() {
myDomInclude = new mxAjax.DomInclude( {source:"testlink"});
}

addOnLoadEvent(function() {init();});

Then in your included content, refer to the object you just created:

<a href="#" onclick="parent.myDomInclude.killPopup('external');">close</a>

mxAjax installation screencast

Arjun has created a screencast dealing with mxAjax installation:

http://www.techscreencast.com/screencast/mxajaxinstallation/mxajaxinstallation.html

mxAjax is a ColdFusion AJAX tookit providing everything necessary to use AJAX on ColdFusion. It's based on Scriptaculous/Prototype and uses JSON as the data transport.

mxAjax and TinyMCE on the same page

I ran into an issue today where I had TinyMCE and an mxAjax control on the same page and they interfered with each other badly. This is to be expected, as Scriptaculous is part of mxAjax and the issue is described here:

http://wiki.script.aculo.us/scriptaculous/show/TinyMCE

So as the wiki explains, the TinyMCE script needs to go first, then the mxAjax scripts, then the mxAjax control init code and then finally the TinyMCE init code.

Multiple mxAjax autocomplete controls

People often want more than one autocomplete control on their page. If you examine the autocomplete example:

function init() {
 new mxAjax.Autocomplete({
   indicator: "indicator",
   minimumCharacters: "1",
   target: "statecode",
   className: "autocomplete",
   paramArgs: new mxAjax.Param(url,{cffunction:"getStateList

"}),
   parser: new mxAjax.CFQueryToJSKeyValueParser(),
   source: "searchCharacter"
 });
}

you will see that the source and destination can be set to whatever you want. So you can just write two blocks

new mxAjax.Autocomplete({
   indicator: "indicator",
   minimumCharacters: "1",
   target: "CONTROL1",
   className: "autocomplete",
   paramArgs: new mxAjax.Param(url,{cffunction:"SOMEMETHOD1"}),
   parser: new mxAjax.CFQueryToJSKeyValueParser(),
   source: "CONTROL1"
 });

new mxAjax.Autocomplete({
   indicator: "indicator",
   minimumCharacters: "1",
   target: "CONTROL2",
   className: "autocomplete",
   paramArgs: new mxAjax.Param(url,{cffunction:"SOMEMETHOD2"}),
   parser: new mxAjax.CFQueryToJSKeyValueParser(),
   source: "CONTROL2"
 });

I've shown two method names in the CFC to be called, as the argument needs to be named the same as the search source control, but this isn't a big problem as both of these can in turn call the real method that does the search (so they are just very simple wrapper methods to allow the search to work). Alternatively you could use two optional arguments named after the controls, in a single component.

MXAJAX - mxData tag basics

The MXAJAX mxData tag is for those using MXAJAX who want complete control over the data (for integration with their own JS controls etc). The tag calls a CFC method or CF function (depending on your setup) and returns JSON corresponding to the CF return type.  In a future post I'll discuss each return type, but for now I'm going to demonstrate how to get at the info as the online example doesn't make it clear.

To parse JSON I recommend using the parser (strangely enough) rather that just evaluating it. It's all packaged with MXAJAX, so you can just go ahead in include it in your page.  Once it's been parsed, you can do with it what you want; the example below (a slightly modified version of the official mxData 1 example) writes the return into a span:

<html>
    <head>
        <title>Returning Data from CF page</title>
        <script type='text/javascript' src='../core/js/prototype.js'></script>
        <script type='text/javascript' src='../core/js/mxAjax.js'></script>
        <script type='text/javascript' src='../core/js/mxData.js'></script>
        <script type='text/javascript' src='../core/js/json.js'></script>
        <script language="javascript">
            var url = "<cfoutput>#ajaxUrl#</cfoutput>";
           
            function init() {
                new mxAjax.Data({
                    executeOnLoad:true,
                    paramArgs: new mxAjax.Param(url,{param:"id=1", cffunction:"getContent"}),
                    postFunction: handleData
                });
               
                function handleData(response) {
                    var myHTMLOutput = JSON.parse(response);
                    document.getElementById("myTargetSpan").innerHTML = myHTMLOutput;
                }
            }
           
            addOnLoadEvent(function() {init();});
        </script>
    </head>
    <body>
        <h1>Returning Data from CF page</h1>
        <span id="myTargetSpan"></span>
    </body>
</html>

The next post will be a more complicated example, doing something with a CF query.

Installing CFAJAX - Paths explained

The official CFAJAX installation steps are at http://www.indiankey.com/cfajax/installation.asp.

However, when installing CFAJAX, no CFADMIN changes are necessary if a few paths are modified to suit the chosen installation. I currently have three versions of CFAJAX on my dev machine, all of which work indepentently if the right files point to the right places. Below I use {webroot} to indicate the root web folder on your webserver.

The installation steps are as follows:

1) Download the CFAJAX version of your choice from http://www.indiankey.com/cfajax/.

2) Inside the zip file you will see a folder named after the version you downloaded (e.g. /cfajax.1.3). Open up this folder and put the various contents into the chosen destination on your server. Assuming, for example, a choice of {webroot}/cfajaxtest, with 1.3 that now gives folders {webroot}/cfajaxtest/app, {webroot}/cfajaxtest/core, {webroot}/cfajaxtest/examples and {webroot}/cfajaxtest/utility (with various files and folders below these). Note that the app, examples and utility folders should be left out of a production server install.

At this point you will note that the examples do not work. Go on, try one. No problem - this is where the path alterations come in. Let's get the first example working; {webroot}/cfajaxtest/examples/text.htm in my case.

3) Alter the following code to match your setup:

* In the file you are trying to run ({webroot}/cfajaxtest/examples/text.htm for this example), the core CFAJAX includes need altering:

<script type='text/javascript' src='/ajax/core/engine.js'></script>
<script type='text/javascript' src='/ajax/core/util.js'></script>
<script type='text/javascript' src='/ajax/core/settings.js'></script>

all become

<script type='text/javascript' src='/cfajaxtest/core/engine.js'></script>
<script type='text/javascript' src='/cfajaxtest/core/util.js'></script>
<script type='text/javascript' src='/cfajaxtest/core/settings.js'></script>

If you want to use a relative path instead, go ahead (but this means you can't just copy and paste beween pages in different directories any more).

* In {webroot}/cfajaxtest/core/settings.js

_cfscriptLocation = "http://localhost/ajax/examples/functions.cfm";

must change to the web location of your CF functions file. In this case it is {webroot}/cfajaxtest/examples/functions.cfm, so I can use

_cfscriptLocation = "/cfajaxtest/examples/functions.cfm";

You can use the fully qualified path with http://blahblah (with a port number if necessary; remember that CF developer runs on port 8500 by default) if you want, but I don't. A purely relative path will cause you problems, since a relative path that is right for one file will be wrong for another.

* In the CF file to which we just pointed, change the top line

<cfinclude template="/ajax/core/cfajax.cfm">

to point to the correct location of the core/cfajax.cfm file

<cfinclude template="../core/cfajax.cfm">

Note that I used a relative location here; absolute locations only work if you have a cf mapping to the location (which I'm assuming we don't).

That's it - the example is now working (yes, I just did a fresh install as I wrote this, as a test). To get other examples working, the example file JS script locations need to be edited too. The "app" directory examples, and some of the other examples like suggest also need some extra tweaking - just check the paths as above and all will be well (although in the case of the "suggest" example, some people have other problems, related to the fact that it uses application variables or some other hassles).

CFAJAX Suggest

CFAJAX 1.2.1 adds a feature called "suggest," a type of autocomplete similar to the email address completion in Google's GMail. Based on the return from a CFAJAX driven CF function, values matching the initial entry into a textbox are shown, so the user can pick from a list rather than typing the whole thing.

(UPDATE: The steps below now include a very simple cf function to return a query to the JS, to ensure people know it really is quite straightforward).

To use the suggest feature, the following are necessary as a minimum:

1) Include the relevant CFAJAX JS scripts in the html (use your own paths as appropriate) e.g.

<script type='text/javascript' src='/cfajax.1.2.1/core/prototype.js'></script>
<script type='text/javascript' src='/cfajax.1.2.1/core/suggest.js'></script>
<script type='text/javascript' src='/cfajax.1.2.1/core/engine.js'></script>
<script type='text/javascript' src='/cfajax.1.2.1/core/util.js'></script>
<script type='text/javascript' src='/cfajax.1.2.1/core/settings.js'></script>

2) Create the suggest object before anything tries to use it (i.e. in the head of the page) and initialise a variable to pass as a search string:

<script language='javascript'>
var mySuggestObject= new Suggest();
var searchString = "";
</script>

3) Create a function to run at the body onload event to initialise the suggest object with the form field that is going to use it:

<script language='javascript'>

function onInit()
{
onSuggestFieldFocus(mySuggestObject);
mySuggestObject.InitQueryCode('mySuggestObject','formfieldname')
}
</script>

4) Create a function called getData() that will do the CFAJAX call (the name of this fucntion is important as it's how the suggest object knows what to do) and a return handler

function getData(qry)
{
searchString = qry;
DWREngine._execute(_cfscriptLocation, null, 'myCFFuction', searchString, getDataResult);
}

function getDataResult(return)
/*
this function assumes a CF query object with columns USERNAME and DN is coming back from the database via the CF function
as the username is typed, the full DN is displayed as the suggest option
*/
{ var key = Array();
var value = Array();
for (i=0; i < return.length; i++)
{
key[i] = return[i].USERNAME; //if your query has a different column name, use it here
value[i] = return[i].DN; //if your query has a different column name, use it here
}
strQuery = selectedSuggestObject.name + '.showQueryDiv("' + searchString + '", key , value)'; eval (strQuery);
}

5) In the form, the form element needs to call the onSuggestFieldFocus() function e.g.:

<input id="formfieldname" name="formfieldname" value="" size=20 autocomplete="off" onFocus="onSuggestFieldFocus(mySuggestObject)">

6) Make sure the CF function in your functions.cfm file returns a query, for example:

<cffunction name="myCFFuction" returntype="query" output="no">
<cfargument name="searchString" type="string" required="yes">
<cfset var QGetMatchingUsers="">
<cfquery name="QGetMatchingUsers" datasource="myDSN">
SELECT username,dn
FROM users
WHERE username LIKE <cfqueryparam cfslqtype="cf_sql_varchar" value="#ARGUMENTS.searchString#%">
</cfquery>
<cfreturn QGetMatchingUsers>
<cffunction>

That's it. Compare this with the basic example on the CFAJAX site for the little differences:

http://www.indiankey.com/cfajax/examples/suggest/example1.htm

A CF error handler for CFAJAX calls

When calling a CF function via CFAJAX, CF errors can be hard to debug. This technique allows a useful error message to be displayed via a popup, speeding the fault diagnosis:

1) In the CF functions page you are calling via cfajax, add a cferror tag:

<cferror template="error.cfm" type="exception">

2) In the location pointed to by the cferror tag, put as error handling code:

<cfsetting showdebugoutput="false">
<cfoutput>alert("#JSStringFormat("Error:" & Error.Diagnostics)#");</cfoutput>

Now CFAJAX thinks everything is ok when a CF error occurs; it renders the return to the page instead of the normal CFAJAX return JS (which would be nothing because an error occurred) and the popup shows.

Getting the CFAJAX Suggest examples to work locally

Some people have trouble getting the bundled CFAJAX suggest examples to work on their own server, even though the other examples work ok:

1) The suggest example relies on application variables, so if you don't have a cfapplication tag setup for the examples directory (perhaps in Application.cfm), do so.

2) If the other examples are working, you probaby have your paths set up correctly, but double check the javascript paths in the example file to make sure.

3) The functions.cfm file uses CGI.PATH_INFO to find the script name - this should be changed to CGI.SCRIPT_NAME, as Apache and probably some other servers don't populate CGI.PATH_INFO. You'll find this in the function getStateList() and in many other functions in the file. I also recommend CGI.HTTP_HOST rather that CGI.SERVER_NAME, because server_name can be incorrect on multi-homed servers.

Make these changes and it will work - I did it on a new install to make sure and other users have confirmed this fixes their problems.

CFSAVECONTENT in CFAJAX calls

When a multiline string is returned from a CF function used with CFAJAX, the engine seizes on the line breaks. This is easy to work around using ReReplace():

<cfset MyReturn = ReReplace(MyReturn,"[#CHR(10)##CHR(13)#]","","ALL")>

There is a second issue with CFSAVECONTENT specifically, in that the core CFAJAX include has this in it:

<cfsetting enablecfoutputonly="yes">

which means you need to add CFOUTPUT tags around your content inside the CFSAVECONTENT tag.

<cffunction name="TestSaveContent" returntype="string" output="true">
<cfset var MyReturn = "">
<cfset var MyString = "Hello">
<cfsavecontent variable="MyReturn">
<cfoutput>
<p>#MyString#</p>
<p>Goodbye</p>
</cfoutput>
</cfsavecontent>
<cfset MyReturn = ReReplace(MyReturn,"[#CHR(10)##CHR(13)#]","","ALL")>
<cfreturn MyReturn>
</cffunction>

CFAJAX Return Types

CFAJAX 1.2 can return CF strings, numerics, booleans, arrays, structures, queries and CFCs. Each of these has a corresponding JS object.

String

The return for a CF string is a JS string. No surprises there.

Numeric

Numerics returned from CF become JS strings, as you will see if you return a simple numeric and then use the + operator in JS to try to add something to it. Convert the return in JS to a number first, using whichever method you prefer (the unary + is the quickest, e.g. var myCalc = +return + 23;)

Boolean

CFAJAX returns YES and NO (note the CAPS) for boolean true and false, which need converting for use in JS as booleans.

Array

Only single dimension arrays are supported. CF arrays start at index 1, while JS arrays start at index 0, so remember to subtract 1 from the CF array index in your JS code. Other than that it's straight forward.

Structure

A CF struct comes back as an array of objects. Each object has two properties, KEY and VALUE (note the CAPS; the key itself is also in CAPS as in the example), representing the key and value of a member in the CF struct; the array contains each of those members e.g.:

CF

<cfset mystruct = StructNew()>
<cfset mystruct.mynumber = 1>
<cfset mystruct.mystring = "hello">
<cfreturn mystruct>

JS

theStructNumberKey = return[0].KEY // returns "MYNUMBER"
theStructNumberValue = return[0].VALUE // returns "1"
theStructStringKey = return[1].KEY // returns "MYSTRING"
theStructStringValue = return[1].VALUE // returns "hello"

Query

The JS object that represents a CF query is an array of row objects, with each column as a property of each object. E.G. a cfquery that returns columns ID, NAME, DATE, SIZE and has 10 rows would become a JS array of length 10, with each element an object with propeties ID, NAME, DATE, SIZE (note the CAPS - all column names are capitalised in the JS object):

myFirstRowID = result[0].ID;
mySecondRowDate = result[1].DATE;
etc

Note that this differs from CF itself, where a query object can be referenced as a set of column arrays:

<cfset myFirstRowID = myQuery["ID"][1]>
<cfset mySecondRowDate = myQuery["DATE"][2]>

CFC

CFC properties declared in THIS (or set in that scope externally) return as JS properties with the names CAPITALISED e.g.:

CF

<cfset THIS.MyNumber = 1>
<cfset THIS.MyString = "Whatever">
<cfreturn THIS>

JS

theCFCMyNumber = return.MYNUMBER // returns "1"
theCFCMyString = return.MYSTRING // returns "whatever"

To facilitate setting up and returning a CFC externally to the CFC code, just create an CFC file called "cfobject.cfc", containing only empty tags, in the directory of your functions.cfm file and call it this way:

<cfset object = CreateObject("Component","cfobject")>
<cfset object.MyNumber = 1>
<cfset object.MyString = "Whatever">

The above techinque can be seen in the zip lookup examples.

Calling a Coldfusion Function with CFAJAX

CFAJAX allows you to call, via Javascript, CF functions you have defined in a CF file which will run server-side and return info to the page. In CFAJAX 1.2, the call syntax changed a little from previous versions; the basic format is:

DWREngine._execute(_cfscriptLocation, null, 'SomeCFFunctionHere', Argument1, Argument2......., resultHandler);

In the above line:

* _cfscriptLocation is the web location of ColdFusion file that contains the CF functions you wish to call. This location is usually set in the CFAJAX file settings.js. It can be absolute from the web root (e.g. /cfajax/core/settings.js) or a full http://... URL (it can also be relative to the calling page but that will just cause hassles when you create pages in other locations).

* null - pass this in every cfajax call and leave it as null.

* 'SomeCFFunctionHere' is the name of the CF Function you want to call inside your functions file.

* Argument1, Argument2... are the arguments you want to pass to the CF function. Make sure you get the order right.

* resultHandler is the JS function in your page that will handle the return result when CFAJAX comes back to the page.

So, for example, If my CF Function was:

<cffunction name="AddOne" returntype="numeric">
<cfargument name="InputNumber" required="yes" type="numeric">
<cfreturn ARGUMENTS.InputNumber + 1>
</cffunction>

I'd call it like this:

var myNumber = 23; DWREngine._execute(_cfscriptLocation, null, 'AddOne', myNumber, resultHandler);

and I'd get 24 as a return

CFAJAX Security Flaw

Rick Root discovered a flaw in the way CFAJAX handles string inputs. The flaw allows a remote user to execute arbitrary CF functions on the host server.

See his blog for full details and for a fix:

http://www.opensourcecf.com/1/2006/02/Security-Flaw-in-CFAJAX.cfm

Fusion Authority Article on AJAX

Part one of my article on AJAX is available at Fusion Authority: http://www.fusionauthority.com/Techniques/4593-Using-AJAX-with-ColdFusion-Part-I

The article describes the basics of AJAX and summarizes some important AJAX tools for ColdFusion developers. An example of doing a "two releated selects" technique with CFAJAX is given.

Part two will break down and describe the example and provide some extra features.

BlogCFC was created by Raymond Camden. This blog is running version 5.5.1.