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 »
11,246 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 »
11,246 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 »
11,246 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
Ben Nadel's Company - Epicenter Consulting Recent Blog Comments
May 24, 2013 at 5:39 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Adam Oops! My mistake! I hadn't gotten that far in my testing - I'm still baby stepping my way through the process. ... read »
May 24, 2013 at 5:13 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
Hi Jason, Thanks for checking up on that, but I still stand firm on my position. :) There are actually two listLast()'s in use, and you're right that the one using a space as a delimiter is fine. ... read »
May 24, 2013 at 4:45 PM
Ask Ben: Manually Enforcing Basic HTTP Authorization In ColdFusion
@Ben I have been lurking your site for quite some time, and haven't stepped up to comment until today. Thanks for all the great info - keep it up! @Adam I believe you are mistaken... as the commen ... read »
May 24, 2013 at 11:21 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@WebManWalking, Ha ha, let's us never speak of justifying "##" notation again :P ... read »
May 24, 2013 at 11:18 AM
Strange Interaction Between DeserializeJson(), ArrayContains(), And Database Values In ColdFusion
@Ben, Ah, so it was indeed how I vaguely remembered it to be: A direct assignment value = users.id[ i ] causes value to retain the sticky datatype of the query column. Although unnecessary in ... read »
May 24, 2013 at 9:11 AM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Brandon, Hi, No, I haven't been able to do that. I have just kept it as it is. ... read »
May 23, 2013 at 9:52 PM
Preventing Links In Standalone iPhone Applications From Opening In Mobile Safari
@Muhmmadibn Did you figure out a solution to launching PDFs? I am running into the same issues myself. There is no way to close the PDF or go back once you launch it. Thanks in advance! ... read »
May 23, 2013 at 6:06 PM
The Girl Who Broke My Heart, And Made Me A Better Person
Good day,ladies and gentle men, my name is Dr AMADI the great spell caster in Africa, i have help so many people for different kind of problems,who say there is no solution to problems on earth, that ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools