ColdFusion Splendor – When to use invokeCFClientFunction

I have seen some confusion when it comes to using invokeCFClientFunction. I have been asked this question a few times, more recently on LinkedIn, so I thought explaining it in a blog post might be a good idea.

If you don’t know already, ColdFusion Splendor has added support for client side CFML (<cfclient>) and this code is translated to JavaScript.  You can call JavaScript functions from cfclient and vice versa.

cfclient also makes calling asynchronous functions of PhoneGap easy by providing synchronous access to them. All device APIs are asynchronous in nature, but in cfclient block you call then as synchronous functions and ColdFusion translates them to asynchronous PhoneGap functions. All function starting with ‘cfclient.’, e.g. cfclient.camera.getPicture(), are asynchronous. In addition to device APIs, data access function, executeQuery and tag, cfquery, are also asynchronous in cfclient.

When you call asynchronous functions in cfclient, ColdFusion takes care of chaining callback functions – any code following an asynchronous function goes in the success callback function. But if you call asynchronous cfclient function form JavaScript code block, then ColdFusion compiler does not touch it. Note that if a UDF in cfclient block calls any asynchronous function (e.g. cfquery or any device APIs) then that function also becomes asynchronous.

Let’s see an example. In the following code, I have a UDF in cfclient block, createDatabase. It does not need any argument, but let’s say it takes one argument, arg1. This function calls queryExecute function, which is an asynchronous function – so createDatabase function also becomes asynchronous. If you call it from JavaScript and have some JS code to be executed only after database is created, then calling createDatabase function directly from JavaScript is not going to work as expected –

<cfclient>
    <cfscript>
	function createDatabase(arg1)
	{
		console.log("arg1 = " + arg1);
		try
		{
			queryExecute(
					"create table if not exists expense (id integer primary key, 
					expense_date integer, amount real, desc text, receipt_path text)",
				[],
				{"datasource":"tempDsn"});
			console.log("Database created");
		}
		catch (any e)
		{
		    console.log("Error : " + e.message);
				return false;
			}
		    return true;
		}

    </cfscript>
</cfclient>

<script >
    ret = createDatabase();
    console.log("After creating database. ret = " + ret);
</script>

There is one console.log in createDatabase function immediately after queryExecute. createDatabase is called from a JavaScript block and after the function call, there is a console.log statement to print return value.

If you execute this code in Chrome/Safari, you will see following output –

arg1 = arg1
After creating database. ret = undefined
Database created 
This is not what you probably expected. You would want database to be created before returning to JavaScript code.

To handle such scenario, where you want to call asynchronous functions declared in cfclient from JavaScript, Splendor provides a function called invokeCFClientFunction. The first argument to this function is name of a (cfclient) function to be called, followed by arguments to that function and the last argument is a callback function, which cfclient will call after executing the asynchronous function.

We will replace JavaScript block above with following code –

<script >
    invokeCFClientFunction ("createDatabase", "arg1", function(ret){
	console.log("After creating database. ret = " + ret);
   });
</script>

If you execute the code now, you will see following output –

arg1 = arg1
Database created
After creating database. ret = true
This output is as expected. cfclient function is completely executed and then subsequent JavaScript code gets executed.

So use invokeCFClientFunction from JavaScript to call asynchronous cfclient functions. You do not need to use it if cfclient function is not asynchronous e.g. –

<cfclient>
    function sayHello(name)
    {
        return "Hello " + name + " !";
    }
</cfclient>

<script >
    console.log(sayHello("Ram"));
    console.log("After calling sayHello");
</script>

Note that ColdFusion decides if a UDF in cfclient is asynchronous after analyzing all the function calls from that UDF recursively – so if UDF func1 in cfclient calls another UDF func2 and if func2 calls a device/database API then func1 is also marked as asynchronous even if it does not directly call any device/database function.

There is a scenario where you don’t need to use invokeCFClientFunction from JavaScript to call asynchronous function in cfclient – when you just want to call-and-forget cfclient function i.e. the JavaScript code following the asynchronous function call does not depend on what that function does.

-Ram Kulkarni

3 Replies to “ColdFusion Splendor – When to use invokeCFClientFunction”

  1. Ram,
    I am unable to connect to any Database inside – Is there a hidden setting to enable the DB access ? code:

    SELECT * FROM all_tables

    Output:
    rror executing sql – could not prepare statement (1 no such table: all_tables)gopitmpFinallyStack[(tmpFinallyStack.length – 1)] is not a function

Leave a Reply

Social