Running Javascript And jQuery In ColdFusion With CFGroovy And Rhino

Posted December 1, 2009 at 8:09 AM by Ben Nadel

Tags: ColdFusion, Javascript / DHTML

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

May 4, 2010 at 5:00 AM // reply »
7 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


May 4, 2010 at 2:01 PM // reply »
10,638 Comments

@Philippe,

Did you download Barney's CFGroovy project? That is required to run this stuff.


May 4, 2010 at 4:09 PM // reply »
7 Comments

@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 :)


May 4, 2010 at 4:27 PM // reply »
7 Comments

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)


May 4, 2010 at 9:14 PM // reply »
10,638 Comments

@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?


May 5, 2010 at 5:30 AM // reply »
7 Comments

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


May 7, 2010 at 10:19 PM // reply »
10,638 Comments

@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.


Post A Comment

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.

Please review the following issues:

Author Name:


Author Email:

Author Website:

Comment:

Supported HTML tags for formatting: <strong>bold</strong>   <em>italic</em>   <code>code</code>







  • Help Wanted - Find Your Next ColdFusion Job
InVision App - Prototyping Made Beautiful With Prototyping Tools Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
Feb 3, 2012 at 10:49 PM
How I Got Node.js Running On A Linux Micro Instance Using Amazon EC2
Wow this was really helpful! Only thing I would add is you need to update your .bash_profile after you edit the secure_path. This is what I did: $ . ~/.bash_profile Otherwise, NPM won't be found. ... read »
Feb 3, 2012 at 10:14 PM
Pushing Base64-Encoded Images Over HTML5 WebSockets With Pusher And ColdFusion
@Ben, Just wanted to let you know that pusher are soon to start limiting sizes on messages. This was the detail that came through in the Feb dispatch: "However, we will soon be limiting the s ... read »
Feb 3, 2012 at 5:05 PM
Regular Expressions Make CSV Parsing In ColdFusion So Much Easier (And Faster)
I tried using your RegEx in my C# program, but it was matching an extra empty-string at the end and so I would end up with an extra field that doesn't exist, so I changed it to this: (^|,)("(?: ... read »
Feb 3, 2012 at 3:47 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
Josh Cyr posted this on Twitter just a little bit ago. Thought it was appropriate. http://stackoverflow.com/questions/1619152/how-to-create-rest-urls-without-verbs/1619677#1619677 ... read »
Feb 3, 2012 at 2:28 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
@Michael, You definitely make a good point (and extra points for quoting movies - I love movies). When you use a return() statement to define the object's public API, it does provide a consistent a ... read »
Feb 3, 2012 at 2:04 PM
Changing The Execution Context Of Your Self-Executing Function Blocks In JavaScript
To quote Jurassic Park: "Just because you can doesn't mean you should". I completely, utterly disagree with the thought that this is more readable. Consider the current module pattern: if ... read »
Feb 3, 2012 at 1:10 PM
REST API Design Rulebook By Mark Masse
@Jordan, Yeah, WRML was created by Mark Masse (author of the book). I also found it to be a bit convoluted. I suppose it is intended to allow the Client to be able to programmaticaly respond to cha ... read »
Feb 3, 2012 at 1:08 PM
ColdFusion Supports HTTP Verbs PUT And DELETE (As Well As GET And POST)
@Jason, To be honest, I don't have good answers for that kinds of stuff. And, to the point, that is specifically why I *really* liked the REST API Design Rulebook by Mark Masse - he just cuts throu ... read »