This is my third post, so already I’m 300% more prodigious than my last attempt at blogging. In that short time, I’ve realised that blogging is very much a two-way communication process – already other people’s comments have helped me develop and enhance my programming knowledge. Barney Boisvert’s comment from my previous post, Using Less CSS with ColdFusion, is a good example, specifically the following sentence:
Andrew, CF8 and above includes the necessary JRE version to natively support executing JavaScript via Rhino with no extra anything.
That was too much of a challenge to resist, so I set myself the task of creating a means of executing less.js (and JavaScript in general) directly from ColdFusion – thus neatly avoiding the need to rely on JARs, and meaning I can just drop in the latest version of less.js as soon as it becomes available. I’ve managed to knock up a basic component to do the job, which I’ll come to in a bit.
Firstly, however, both Barney and Ben Nadel have already blogged about running JavaScript using the former’s CFGroovy project. It’s a great solution as it can run scripts from any JSR-223 compliant scripting language, providing you have the necessary Java implementation available. Scripts run in this way also have transparent access to your ColdFusion scopes. All impressive stuff, and I wish I’d taken a look at implementing this before doing my own thing!
Anyway, for what it’s worth here’s how I managed to get JavaScript up and running in ColdFusion 9, based loosely on the instructions here. The first job is to create a context in Rhino, Mozilla’s JavaScript engine for Java:
<!--- create and enter the runtime context of the executing script --->
<cfset variables.context = CreateObject("java", "org.mozilla.javascript.Context").init().enter() />
<!--- maximum level of optimisation --->
<cfset variables.context.setOptimizationLevel(9) />
<!--- initialise standard objects (Object, Function) and get a scope object --->
<cfset variables.scope = variables.context.initStandardObjects() />
(A quick note here: we’re using the version of Rhino that ships as a library in CF9 – js.jar – and not the cut down version of Rhino found in the JRE itself.)
Then we can take some JavaScript (in this example we’re storing it as a string) and evaluate it. Here’s a ridiculously trivial example:
<cfsavecontent variable="variables.someScript">
var openingText = "I like ";
var middleText = " but only when they're ";
function getSentence(things, colour) {
return openingText + things + middleText + colour;
}
</cfsavecontent>
<cfset variables.context.evaluateString(variables.scope, variables.someScript, "inlineScript", 1, JavaCast('null', '') ) />
We now have a JavaScript function we can call from within CF:
<cfset variables.compiledFn = CreateObject('java', "org.mozilla.javascript.Function").getClass().cast(variables.scope.get("getSentence", variables.scope)) />
<!--- equivalent to JS getSentence("traffic lights", "red") --->
<cfoutput>#variables.context.call(JavaCast('null', ''), variables.compiledFn, scope, scope, ["traffic lights", "red"])#</cfoutput>
The output of this function becomes:
I like traffic lights but only when they're red
As mentioned before, I’ve created a component that encapsulates all this and is available for you to examine as a handy download. When initialised, it will set up a Rhino context for you and provide a handy set of methods for reading and evaluating JavaScript from strings, local files and even URLs. This is more of a proof of concept than anything else and it probably needs some cajoling before it’s ready for a production environment but the principles are there for you to run with. It’s also CF9 only at the moment.
In the component I’ve taken advantage of CF’s onMissingMethod() handler to drop into JavaScript if you’re not calling any of js.cfc’s methods. Therefore you can run JavaScript functions using JavaScript syntax – but in ColdFusion!
<cfoutput>
<!--- call the JavaScript function using the CF method --->
#oJS.callJsFunction('getSentence', ["traffic lights", "red"])#
<br />
<!--- or using the same syntax as you would in JavaScript --->
#oJS.getSentence("traffic lights", "red")#
</cfoutput>
And it works with less.js too. You’ll also need a couple of JavaScript files from Asual’s Java implementation to set up a rudimentary window object and provide a nicer interface, but it works:
<cfset oJS = CreateObject("component", "js").init() />
<cfset oJS.addScriptFromFile(ExpandPath('js/browser.js')) />
<cfset oJS.addScriptFromUrl('http://lesscss.googlecode.com/files/less-1.0.35.min.js') />
<cfset oJS.addScriptFromFile(ExpandPath('js/engine.js')) />
<cfsavecontent variable="inputcss">
@brand_color: #4D926F;
@the-border: 1px;
@base-color: #111;
#header {
color: @brand_color;
}
h2 {
color: @brand_color / 2;
}
#footer {
color: (@base-color + #111) * 1.5;
}
</cfsavecontent>
<h1>Input CSS</h1>
<pre>
<cfoutput>#inputcss#</cfoutput>
</pre>
<h1>Output CSS</h1>
<cfset text = oJS.callJsFunction("compileString", [inputcss]) />
<pre>
<cfoutput>#text#</cfoutput>
</pre>
<!--- cleanup --->
<cfset oJS.exit() />
(although you probably don’t want to grab the less.js file from Google Code each time – far better to store it locally).
Finally, the question that usually pops up at this point is “why the heck do you want to do this?”. After all, you can’t do any DOM manipulation – so anyone thinking of doing a JavaScript version of Super Mario in ColdFusion can walk away disappointed. But I can only speak for myself, and my goal was to run less.js on the server. Yes, there aren’t that many things you can do with server-side JS that you can’t do with ColdFusion. But with the advent of projects such as CommonJS, the need to execute some must-have bit of server-side JavaScript may become more compelling in the future.