After my last post on CF and JS integration (via Rhino), I got several comments that can be summarized as "why?". I addressed the issue in a rather oblique manner in my last post; here's a more full treatment.
Before I start, I want to make incredibly clear that I'm not talking about a new JS-based application server built on Rhino. I'm talking about using JS as an alternate/supplemental syntax for your CFML applications, which still run on the ColdFusion application server, with all the niceties that the CF server provides.
The first reason is that CFML is incredibly verbose for a lot of common tasks. Consider a function definition in CFML:
<cffunction name="doIt" output="false">
<cfargument name="param1" />
<cfargument name="param2" />
</cffunction>
Now the same definition in JS:
function doIt(param1, param2) {
}
The CFML is a lot more verbose, which is both more typing, and more characters to visually parse when you're trying to read the code. Of course, this isn't without downsides. Consider running a parameterized query in CFML:
<cfquery datasource="myDsn" name="get">
select *
from my_table
where id = <cfqueryparam cfsqltype="cf_sql_integer" value="#id#" />
</cfquery>
And now in JS:
var q = new Query("myDsn", "select * from my_table where id = :id");
var get = q.execute({
id: new QueryParam(id, "integer")
});
The CFML implementation definitely highlights the SQL as a standalone language embedded within the markup, while with the JS implementation, the SQL is just a string; there's nothing SQL-y about it. Is the more concise nature of the JS worth it in this specific case? I'd say it's close to a wash: less SQL delineation, but more concise code, more easily reusable SQL, semantically relevant (instead of positionally anchored) parameters, and reusable parameters. Note that in either case, I'm running my parameterized SQL against a datasource configured in the CF Administrator, and the 'get' variable will be a native CF query object (a 'coldfusion.sql.Table' instance).
Now where I really think JS (really ECMAScript, in general) has a lot to offer over CFML is in the language constructs itself. Nested functions, function literals (and their extension: closures) are a singularly fantastic language constructs. Rather than show a single example implemented in both CFML and JS, I'm going to just illustrate the concepts in JS, and contrast them with CFML. Nesting first:
function doSomething(param) {
function f(o) {
// do something ...
}
var result = f(param);
for (var i in result) {
if (typeof i == "object") {
result[i] = f(result[i]);
}
}
}
So what's happening? I'm packaging up some reusable functionality into a function (named 'f'), but it's an implementation detail of the 'doSomething' function, and is therefore internal to the 'doSomething' function. Do do this same sort of "subpackaging" of functionality in CFML, you'd have to put the 'f' function outside as a peer of the 'doSomething' function (probably named 'doSomething_f' or whatever). That's a leak in encapsulation, as well as unnecessarily crowding the external namespace.
Function literals implicitly require function variables, the latter of which CF already has. A function variable just means you can pass around a function as a variable, and then call it later. A function literal, however, means you can define a function without declaring it. A simple example:
function declaredFunction() {
}
literalFunction = function() {
};
In the former case, there is an actual declaration of the function. It is it's own statement, and is compiled (or interpreted) as such. The latter, however, is a literal. It's an expression, and can be placed anywhere you might want to use an expression. Here's another example of doing something with declarations and then literals:
// declarations
function optionOne(){}
function optionTwo(){}
return (x == y) ? optionOne : optionTwo;
// literals
return (x == y) ? function(){} : function(){};
Assuming this is within another function, the declared functions in the first section could be nested, thereby minimizing their effect on namespace crowding. The second section, of course, doesn't have any extra declarations, though if the functions have any substantial size to them, it can get unreadable very quickly. As such, the latter syntax isn't always the right one, but it is nice to have the option. With CFML, you're again forced to explicitly declare the functions as peers.
And now closures. Closures are nested functions (declared or literal), which execute in the context of their definition location, even if they're executed after that context has gone away. Here's a (somewhat contrived) example:
function getFilter(name, value) {
return function(item) {
return item[name] == value;
}
}
function conditionallyDoSomething(collection, filter) {
for (var i = 0; i < collection.length; i++) {
if (filter(collection[i]) {
// do something ...
}
}
}
conditionallyDoSomething(people, getFilter("age", 27));
This is a fairly contrived example, as I said, but it illustrates the concept. By the time the function returned from 'getFilter' is executed, the 'getFilter' function has terminated. However, it (the function literal) still has access to the context it was defined in – namely the 'getFilter' function's context, and therefore it's parameters. Closures are really the crown jewel of ECMAScript, in my opinion. With CFML, the closure is totally undoable, unless you use some sort of synthesis framework like Sean's.
The final advantage, like the querying example above, cuts both ways. JS-based implementations can't use any existing CFML tools (Fusebox, ColdSpring, etc.), but they can use all the myriad JS tools that are available. I happen to be a big fan of Prototype, and I can use the UI-agnostic pieces (like the Object and Array extensions) of Prototype on the server-side. Similarly, if you write some wicked-cool routine for your server-side processing, you can immediately and directly reuse it in your client-side JS (or quite possibly AS).
So between the enormously lighter language syntax and the additional language functionality, I think there's a compelling case for using JavaScript (or ECMAScript) as a language for building apps on the ColdFusion server. In an ideal world, it's be ECMAScript 4th Edition (what ActionScript 3 is based on), but there isn't a mature, Java-based interpreter for it anywhere. Aside from just generally being more modern, the 4th Edition provides strong typing (using colon-postfix notation), real packages and classes, interfaces, object member access levels, and a few other neat things.
One very interesting aspect of all this is that Adobe happens to own an ECMAScript 4 compiler (the AS3 compiler from the Flex SDK), an ECMAScript 4 runtime (the Tamarin project that underlies Flash Player 9 and is now hosted by the Mozilla Foundation), and ColdFusion. So it seems like using ECMAScript 4 as an alternative to CFML for the CF Server is really not that outlandish. The pieces are all done, it's just the integration that is lacking, and CF is all about integration. Rather to rich of a project for me to undertake on my own (hence ECMAScript 2 and Rhino), and the closed nature of CF makes it rather hard as well, but for an entity like Adobe, quite a reasonable undertaking, I'd think. Here's hoping for CF9!