Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at BFusion / BFLEX 2009 (Bloomington, Indiana) with: Andy Matthews and Steve Withington and Rob Huddleston

Running Javascript And jQuery In ColdFusion With CFGroovy And Rhino

Posted by Ben Nadel

After I found out that CFGroovy could execute Javascript on the ColdFusion server, it was only natural that I was curious about executing jQuery on the ColdFusion server. As it turns out, this is possible, but decidedly more complicated. As I stated in my last blog post, the version of Rhino that comes packaged with the JRE has no concept of browser-based Javascript constructs such as the Window or the Document.

To deal with this limitation, John Resig has created the Env.js project. Env.js updates the Rhino global scope to include functionality that mimics the window and the document and all the good stuff that goes along with a browser-based implementation of Javascript (including AJAX load functionality). But of course, it's not quite as simple as including the env.js Javascript file; env.js only works in the newer releases of Rhino (1.6r6+), which are newer than the Rhino that comes packaged with the JRE (1.6r2).

To get jQuery to work, we need to manually load a newer, modified version of Rhino (env-js.jar) created by the Env.js team. To do that, rather than overwrite what we have in our Java classpaths, we are going to use a URL Class Loader; of course, once we have to go that route, I find it easier to drop down into Groovy (using CFGroovy) so that I can create Java objects without all kinds of explicit typing.

This example is a bit long, so bear with me; I have tried to comment it as well as I could. The Javascript code is at the top in the CFSaveContent. The code to load Rhino, bind the ColdFusion scopes, and execute the Javascript is below that.

  • <!--- Import the CFGroovy tag library. --->
  • <cfimport prefix="g" taglib="../cfgroovy/" />
  •  
  •  
  • <!---
  • Let's create and store some Javascript and jQuery code that
  • we want to run on the ColdFusion server (using Rhino).
  • --->
  • <cfsavecontent variable="javascriptCode">
  •  
  • <!---
  • Inlucde John Resig's ENV-JS project. This will update
  • the Rhino Javascript environment to have the items you'd
  • expect in a Browser-based implementation of Javascript
  • that are required for the jQuery library to run as-is
  • (ex. window, document).
  • --->
  • <cfinclude template="./env.rhino.js" />
  •  
  • <!---
  • Default the location to *something* otherwise the jQuery
  • library will break since there is no page loaded yet.
  • --->
  • window.location = "about:blank";
  •  
  • <!---
  • Include the jQuery library.
  •  
  • NOTE: This library is loaded AS-IS and has not been
  • modified for use within Rhino.
  • --->
  • <cfinclude template="./jquery-1.3.2.js" />
  •  
  •  
  • <!--- ---------------------- --->
  • <!--- ---------------------- --->
  •  
  •  
  • <!---
  • Grab the ColdFusion array.
  •  
  • NOTE: We will bind the Variables and the Request scopes
  • to the Javascript global scope of execution before we
  • run this Javascript code.
  • --->
  • var cfArray = variables.get( "cfArray" );
  •  
  • <!--- Create a Javascript array. --->
  • var jsArray = [ "Katie", "Tricia", "Joanna", "Libby" ];
  •  
  •  
  • <!---
  • Using jQuery's each() method, iterate over each of the
  • items in the JS array and push them onto the ColdFusion-
  • based array.
  • --->
  • $.each(
  • jsArray,
  • function( index, value ){
  •  
  • <!---
  • Push the value on the ColdFusion array (but
  • modify it slightly).
  •  
  • NOTE: We are using the underlying Java methods
  • on the ColdFusion array, not arrayAppend().
  • --->
  • cfArray.add( value + " is a hottie! (From JS)" );
  •  
  • }
  • );
  •  
  • </cfsavecontent>
  •  
  •  
  • <!---
  • Create the ColdFusion array that will be used in the
  • Javascript context.
  • --->
  • <cfset cfArray = [ "Sarah is a hottie! (From CF)" ] />
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Get the current directory path. This will be used so we
  • don't have to use expandPath() on the JAR file paths.
  • --->
  • <cfset currentDirectory = getDirectoryFromPath(
  • getCurrentTemplatePath()
  • ) />
  •  
  • <g:script>
  •  
  • <!---
  • Create a new Groovy class loader. We will need to create
  • a new class loader since we are loading custom JAR files.
  • --->
  • def classLoader = new GroovyClassLoader( null );
  •  
  • <!---
  • Add the latest Rhino JAR file that has been modified by
  • the jQuery team to play nicely with the env.js project.
  • --->
  • classLoader.addURL(
  • new URL( "file:///" + variables.currentDirectory + "env-js.jar" )
  • );
  •  
  • <!--- Add the other Rhino JAR file. --->
  • classLoader.addURL(
  • new URL( "file:///" + variables.currentDirectory + "js-14.jar" )
  • );
  •  
  •  
  • <!---
  • Use the new class loader to create the Rhino shell Global
  • object as the context binding scope for our Javascript
  • evaluation. We need to do this since ENV.js uses shell-
  • based methods (such as the LOAD() method) that are not
  • available outside of the Shell.
  • --->
  • def globalScope = Class.forName(
  • "org.mozilla.javascript.tools.shell.Global",
  • true,
  • classLoader
  • )
  • .newInstance()
  • ;
  •  
  • <!---
  • Use the new class loader to create and enter a new context
  • for our Javascript evaluation.
  • --->
  • def scriptContext = Class.forName(
  • "org.mozilla.javascript.ContextFactory",
  • false,
  • classLoader
  • )
  • .newInstance()
  • .getGlobal()
  • .enterContext( null )
  • ;
  •  
  •  
  • <!--- Initialize the global scope. --->
  • globalScope.init( scriptContext );
  •  
  • <!---
  • Set the optimization of the context to -1. This will
  • ensure that the script is evaulated each time and not
  • compiled and cached???
  • --->
  • scriptContext.setOptimizationLevel( -1 );
  •  
  • <!---
  • Initialize the context's standard object with the global
  • scope as the execution context.
  • --->
  • scriptContext.initStandardObjects( globalScope );
  •  
  •  
  • <!---
  • Bind the ColdFusion scopes to the Javascript execution
  • context so that they can be accessed via Javascript.
  • --->
  • globalScope.putProperty( globalScope, "request", request );
  • globalScope.putProperty( globalScope, "variables", variables );
  •  
  •  
  • <!---
  • Take the Javascript code we defined above and evaluate
  • it within our new Javascript script engine.
  • --->
  • scriptContext.evaluateString(
  • globalScope,
  • variables.javascriptCode,
  • "cf-rhino-script",
  • 0,
  • null
  • );
  •  
  • </g:script>
  •  
  •  
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  • <!--- ----------------------------------------------------- --->
  •  
  •  
  • <!---
  • Now that the Javascript has executed in the latest verion of
  • Rhino, our ColdFusion Array should have been updated. Let's
  • output the array to see how it was modified.
  • --->
  • <cfdump
  • var="#cfArray#"
  • label="ColdFusion Array (Modified By Javascript)"
  • />

At the beginning of my Javascript code, I am included the env.rhino.js file such that the Rhino environment will mimic a browser environment. After that, I have to explicitly set the window.location to something; if I don't, including the jQuery library will error during initialization. Once I have included the jQuery library, I can successfully run jQuery code on the server. In this case, I am pushing values from a Javascript-based array onto a ColdFusion-based array. When we run the above code, we get the following output:

 
 
 
 
 
 
Running jQuery On The ColdFusion Server With Env.js, Rhino, And Groovy. 
 
 
 

Very neat! As you can see in the output, values coming from the jQuery / Javascript context have "(From JS)" appended.

Granted, there is some ooh-ahh factor here; but, not only is this more complicated than running the pre-bundled Rhino implementation, it's also much much slower. The above example takes about seven seconds to run on my server. And, based on the speed of my previous blog post, I have to assume that the bulk of this slowness comes from the parsing and translation of the env.rhino.js and the jquery-1.3.2.js files. To speed things up, I might be able to reuse the global scope of the execution context - but that's a blog post for another time.




Reader Comments

I can't run your example, not sure I have downloaded the right files or setup the right directory structure.
Could you guide me a little bit ?
Thanks in advance,
Philippe

Reply to this Comment

@Ben Nadel,

yes i have all the previous examples working, i m a little confused about env.rhino.js i think it might be the problem.

thank you for answering :)

Reply to this Comment

I tried a lot of things, believe me ;)

the error I have now is this one, not sure what's going wrong

error instantiating (Wrapped java.lang.NullPointerException (cf-rhino-script#653)): class java.lang.Runnable is interface or abstract (cf-rhino-script#653)

Reply to this Comment

@Philippe,

It definitely got me a bit of finageling to get this example working. When you want to Env.js. stuff to run, you have to load a new version of Rhino; the default one that comes installed with Java does not support all of the stuff that is required to run the given Javascript (there is a lot of implicit getter/setter stuff that is not supported).

That's why I am loading the env-js.jar. I *believe* that this loads the newer version of Rhino to allow jQuery to run.

Does that help at all?

Reply to this Comment

Not really, I have added these .jar files, always get errors.
I'm gonna stick to xmlParse to manipulate my xml files, a little sad I couldn't try with jQuery :(
Thank you anyway
Regards,
Philippe

Reply to this Comment

@Philippe,

jQuery is great, but I also happen to have a love affair with XML parse and XPath for XML querying. If you get stuck on that, let me know.

Reply to this Comment

Post A Comment

?
You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
Comment Etiquette: Please do not post spam. Please keep the comments on-topic. Please do not post unrelated questions or large chunks of code. And, above all, please be nice to each other - we're trying to have a good conversation here.