Skip to main content
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Henke
Ben Nadel at cf.Objective() 2010 (Minneapolis, MN) with: Mike Henke

Running Javascript In ColdFusion With CFGroovy And Rhino

By
Published in , Comments (14)

As Barney Boisvert has demonstrated with his CFGroovy custom tags, scripting in Java is surprisingly easy. As he explains in the CFGroovy project, any scripting language that is JSR-223 compliant can be executed inside of a Java-based context with mostly-seamless communication between the Java context and the scripting context. The CFGroovy project was designed originally (I think) to work with Groovy; but, it can now be used run code through any JSR-223 compliant script engine implementation in your Java classpaths.

Because the set of available script engine implementations depends on your JRE, we need to ask Java to see what's available. We can get this information from the Javax script engine manager:

<!--- Get the script manager class. --->
<cfset scriptManager = createObject(
	"java",
	"javax.script.ScriptEngineManager"
	) />

<!--- Get the factories that are natively available. --->
<cfset scriptFactories = scriptManager.getEngineFactories() />

<!---
	Loop over the factories and output the names that each
	can build.
--->
<cfloop
	index="scriptFactory"
	array="#scriptFactories#">

	<!--- Output factory name and version. --->
	<cfoutput>
		[
		#scriptFactory.getEngineName()# -
		#scriptFactory.getEngineVersion()#
		]

		<br />

		<!---
			Output the names that this factory will respond to
			(the names for which it can create valid script
			engines).
		--->
		<cfloop
			index="alias"
			array="#scriptFactory.getNames()#">

			- #alias#<br />

		</cfloop>

	</cfoutput>

</cfloop>

Here, we are asking the script engine manager for its factories, and then, from each factory, we ask it for the list of "names" for which it can create script engines. When we run the above code (on my personal JRE), we get the following output:

[ Mozilla Rhino - 1.6 release 2 ]

  • js
  • rhino
  • JavaScript
  • javascript
  • ECMAScript
  • ecmascript

As you can see, my script engine manager will respond to js, rhino, JavaScript, javascript, ECMAScript, and ecmascript. It is important to understand that I have not explicitly installed any Rhino package into my Java classpaths. It appears that Rhino's 1.6r2 implementation comes pre-packaged with the JDK 6 and JRE 6 libraries. It also appears that Rhino is the only script engine implementation that comes pre-packaged.

This is very cool! Now that we know that Rhino is packaged with the JRE, let's run some Javascript in ColdFusion using CFGroovy:

<!--- Import the CFGroovy tag library. --->
<cfimport prefix="g" taglib="../cfgroovy/" />

<!--- Execute Javascript (on the Server). --->
<g:script lang="JavaScript">

	<!---
		Create the super constructor for the Person class.
		This will create an object with private variables
		and getters / setters.

		Not only will this test for object functionality, it will
		test to make sure the concept of lexically-bound variables
		remains true even inside the ColdFusion context.
	--->
	function Person( name ){
		var _name = name;

		return({
			getName: function(){
				return( _name );
			},

			setName: function( name ){
				_name = name
				return( this );
			}
		});
	};


	<!--- Create a new person. --->
	var katie = Person( "Katie" );


	<!---
		Store a message value into the ColdFusion Variables
		scope; this value is just a simple string.
	--->
	variables.put( "message", (katie.getName() + " is hilarious!") );

	<!---
		Store the new Person instance back into ColdFusion.
		For this, we need to use the Java-native methods
		calls for the Variable scope's hash table.
	--->
	variables.put( "katie", katie );

</g:script>


<!---
	Output the message we stored. Since the original value is
	a simple string, it has been automatically converted into
	a Java string that we can use as-is.
--->
<cfoutput>
	Message: #message#<br />
</cfoutput>


<!---
	Now, that "katie" object is stored in the variables scope,
	let's get the Name property. Unfortunately, the Java
	representation of a JavaScript object is not exactly as it
	is in Javascript - it's a simulation. As such, we need to
	fenagle a bit to use the object in the ColdFusion context.
--->
<cfset name = katie.callMethod( katie, "getName", arrayNew( 1 ) ) />

<cfoutput>
	Name: #name#<br />
</cfoutput>

Here, we have some Javascript code that defines a class, "Person," and instantiates it. Then, both the class instance and a simple message are stored back into the ColdFusion Variables scope. Notice that I can't simply use property-dot-notation (ex. variables.katie) to store values into the Variables scope - I have to use the Java-based put() method. While I don't demonstrate it, I would also have to use the get() method to gather information from the ColdFusion objects bound to the Javascript context.

When we run the above code, we get the following output:

Message: Katie is hilarious!
Name: Katie

Very cool! As you can see, simple values (such as strings) that are passed from the Javascript context to the ColdFusion context are easy to deal with. More complex objects, like Javascript class instances, are not so easy to deal with - they don't map directly to objects as we typically think of them; rather, they are the Java-based implementation of the Rhino-based Javascript object simulations.

What's very cool, though, is that even outside of the Javascript context, the Javascript methods are still lexically-bound. Meaning, the methods defined in the Person() pseudo constructor have access to all of the variables (ex. _name) that were also defined in the Person() local scope. This is one of the greatest features of Javascript and it's exciting to see that it hold true even after the Javascript objects leave the Javascript context.

I don't really know why I would want to run Javascript in ColdFusion, but it's pretty cool that I can for funzies. Take note, however, that Rhino does not offer a full simulation of Javascript; there is no sense of a Window or a Document object or anything else that is directly tied to a browser-based implementation of Javascript. Of course, that can be fudged, which I'll talk about in a later post.

Want to use code from this post? Check out the license.

Reader Comments

25 Comments

Nice article, Ben. It's worth mentioning that you can run the non-DOM pieces of standard JS frameworks on the server-side without any fuss. I was doing this with the Prototype core back in the CFRhino days. So you can very easily use some of the tooling you use client side on the server.

Also, the Rhino implementation that ships with the JRE is not the full Rhino implementation. They've removed E4X support, specifically, along with a few other changes. Sun has published a list of things they've changed, but I'm not sure where that's available. However, if you want to use a different version of Rhino, you just have to drop the JAR on your classpath and you'll be set. And in fact, chances are good you're not using the Sun-packaged Rhino anyway; ColdFusion ships with at least two different versions of Rhino inside.

15,848 Comments

@Barney,

Ah, good point; since I've only really used jQuery as my primary Javascript library, it didn't occur to me try anything else.

In my next post, I am going to cover what is needed to run full jQuery using John Resig's env.js project.... significantly more complicated than the above example. Ironically, it still uses CFGroovy since Groovy just makes it so much easier to create class loaders and other Java-like constructs.

13 Comments

http://java.sun.com/javase/6/webnotes/

"E4X (ECMAScript for XML - ECMA Standard E4X) has been excluded. Attempting to use XML literals in JavaScript code will result in a syntax error. This feature depends on the XMLBeans library."

Great work Ben, one step closer to my dream of server-side JavaScript in a CF context :)

13 Comments

@Peter thank you so much for that link! I had no idea there were others with my CFJS desire let alone one building it. I added a request for it to Railo's user voice site and not a single user other than a guy I work with voted for it!

Now if only it can be done on Railo too then there would be a clear reason for Adobe to build it into CF10 and then CFJS can take over the world! :)
(at least in my day dreams)

8 Comments

I am having trouble with a super simple example:

<cfimport prefix="g" taglib="/../lfcomponents/customtags/cfgroovy2/">
 
<cfset variables.message = 'original'>
 
<g:script lang="JavaScript">
variables.message='Set in Javascript';
</g:script>
 
<cfoutput>
#variables.message#
</cfoutput>

I get the error "Java class "coldfusion.runtime.PageScope" has no public instance field or method named "message". (#1)"

Are there any docs for groovy? How do I get variables in and out of Groovy?

Most importantly, can I create a variable within the Groovy context, without first defining it in CF, and have it available to CF after Groovy has completed its execution?

8 Comments

After looking at some more examples, one of Ben's examples, that I tried (and that works):

<g:script>
	variables.myString = "JavaScript is lovely!";
</g:script>

But, when I lang="js":

<g:script lang="js">
	variables.myString = "JavaScript is lovely!";
</g:script>

The same code throws an error:

"Java class "coldfusion.runtime.PageScope" has no public instance field or method named "myString". (#1)

So, are there some rules or docs for using the JS engine? I can't seem to create variables within Groovy and get them back to CF. Any ideas?

8 Comments

Guess I should have read a little bit closer as the way to get CF vars in and out is described above:

Notice that I can't simply use property-dot-notation (ex. variables.katie) to store values into the Variables scope - I have to use the Java-based put() method. While I don't demonstrate it, I would also have to use the get() method to gather information from the ColdFusion objects bound to the Javascript context.
I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel