Dynamically Evaluating Image Functions In ColdFusion 8
I was playing around with some ideas when I came across an odd behavior with ColdFusion's Evaluate() method. Now, let me start by saying that I hate the Evaluate() method with a passion and I cringe at the idea of using it; however, I was doing something that involved dynamic execution of built-in ColdFusion methods and Evaluate() is the only way I know of as to how to make that happen (without manually writing out all the use-cases).
So anyway, I went to try to use a ColdFusion 8 image function within Evaluate(). The first one I tried was ImageGetWidth(). This worked just fine. But then, I tried ImageBlur():
<!--- Read in image. --->
<cfset objImage = ImageNew( "./kissy_face.jpg" ) />
<!--- Dynamiclly call blur method on given image. --->
<cfset Evaluate( "ImageBlur( objImage )" ) />
... and ColdFusion threw the following error:
coldfusion.runtime.CfErrorWrapper : null null
Seeing the "null null", I figured maybe it had something to do with the fact that ImageBlur() doesn't return a value (while ImageGetWidth() does). That's when I started looking around for other ColdFusion methods that don't return a value and realized that image manipulation functions seemed to be unique in this regard.
And so, I built my own method that returned a null value and tried to dynamically execute it:
<cffunction
name="GetVoid"
returntype="void"
output="false">
<cfreturn />
</cffunction>
<!--- Dynamically evaluate void-function. --->
<cfset Evaluate( "GetVoid()" ) />
This runs without error. Apparently, it has nothing to do with whether a value is returned (unless of course built-in functions returns a different kind of VOID than a ColdFusion user defined function).
I started to Google around for some possible answers. I had remembered seeing people use a whole bunch of crazy Java to try and do things like this, but I never understood what was going on and never bookmarked it. That's when I came across a link from a while back on Elliott Sprehn's blog about getting template paths. In his blog post, Elliott explores the Page object that is available from ColdFusion's GetPageContext() method. After CFDumping out this object, it turns out that in ColdFusion 8, all of the built-in methods are available. I don't ever remember seeing this, so it might be new in ColdFusion 8.
For fun, I tried dynamically evaluating the same method but from this new source:
<!--- Read in image. --->
<cfset objImage = ImageNew( "./kissy_face.jpg" ) />
<!--- Dynamiclly call blur method on given image. --->
<cfset Evaluate(
"GetPageContext().GetPage().ImageBlur( objImage )"
) />
This works just fine. I am not sure why this version works and evaluating the built-in method directly throws a null error. And, I still would love a really nice, clean, easy way to dynamically invoke built-in methods :)
Want to use code from this post? Check out the license.
Reader Comments
Nope, not new in CF8. The Page object has always had the CF functions. This is because the foundation unit of compiled .cfm and .cfc files is the Page object.
That is, a test.cfm is going to compile into a Java class that extends the coldfusion.runtime.CFPage class, and your calls to functions like ListGetAt() are going to be transformed into a call to the instance method ListGetAt(). This makes transforming cfml code into Java really easy as there's a 1-1 mapping between CF functions and their implementations inside the compiled class. It also makes the guts of the CF runtime simpler with respect to having a unified foundation type; despite the high level difference between custom tags, cfcs and "regular" .cfm files, they're really the same thing to the engine.
This is also the reason that CF won't let you name functions the same as Java methods since there's ambiguity in the call.
Ever wondered what the compiled class name is for your .cfm file?
<cfdump var="#getClass().getName()#">
or #hashCode()# will give you the hashCode for the current Page.
(See http://java.sun.com/j2se/1.5.0/docs/api/java/lang/Object.html)
In this sense CF has a ton of "secret" functions that are Java methods on the Page.
Generally each CF type has corresponding Java classes that have the real implementations of these functions, and the methods you see in the Page are just proxies to them, or setup some default values.
For instance the coldfusion.image.Image class implements the Image CF type, and has methods that match all the CF image functions.
So the below are all the same:
<cfset ImageBlur(objImage)>
<cfset objImage.blur(3)>
<cfset getPageContext().getPage().ImageBlur(objImage)>
@Elliot,
I was having trouble finding the function methods via GetPageContext() from within a CFC. Any suggestions?
@Ben
Not sure what you mean by function methods. Do you mean the functions you declared with <cffunction>?
@Elliott,
What I mean is that in a CFM page, calling:
GetPageContext().GetPage()
... returns a VERY different object than the same call made from a CFC page. The one called from the CFC page does not seem to have the built-in methods as class methods.
@Elliott,
To be more clear, I want to be able to call:
GetPageContext().GetPage().ImageBlur()
... from within a CFC call, but cannot.
The objects are in fact not different, at least not in that sense. I'm not experiencing the issue you're having either.
The below code illustrates what I'm talking about:
<!---
File: example.cfc
Outside a function there's no magic coldfusion.runtime.CFDummyComponent,
but the Page returned is still of class coldfusion.runtime.CFPage
--->
<cfset variables.actualPage = getPageContext().getPage()>
<!---
Inside a function it's a little different, there's a wrapper around the real
class that represents this page called a "CFDummyComponent", the real
page exists as a package access field on it.
Note that both the CFDummyComponent (which extends CFComponent) and the
actual page extend CFPage, so the CF functions exist on both.
--->
<cffunction name="test">
<cfset var Page = getPageContext().getPage()>
<cfset var actualPageField = Page.getClass().getDeclaredField("actualPage")>
<cfset var actualPage = "">
<cfset var image = ImageNew("http://www.google.com/images/logo_google_suggest.gif")>
<cfset actualPageField.setAccessible(true)>
<cfset actualPage = actualPageField.get(Page)>
<!--- These are all exactly the same, we get a 4 times blurred image --->
<cfset actualPage.ImageBlur(image)>
<cfset variables.actualPage.ImageBlur(image)>
<cfset Page.ImageBlur(image)>
<cfset ImageBlur(image)>
<cfimage action="writetobrowser" source="#image#">
</cffunction>
@Elliott,
Very interesting. When I CFDump out the GetPageContext().GetPage() in the pseudo-constructor, I do NOT get the built-in ColdFusion methods. However, when I go to USE them, they work fine. It must just not be outputting the data even though it exists. There's probably some "conflict" between the data about the component and about the Java class that ties it all together (and CF is erring on the CF-related data rather than the Java data).
Thank you so much for the tip. I got an interesting little experiment going on here that I will blog about tomorrow.
@Ben
You *DO* get the cffunctions, cfdump just isn't printing them, and instead prints that pretty red cfcomponent box with the information about the component.
Try:
<!---
File: example2.cfc
Print out all the CF functions.
--->
<cfset methods = getPageContext().getPage().getClass().getSuperClass().getMethods()>
<cfloop array="#methods#" index="i">
<cfset writeOutput(i)><br>
</cfloop>
They're all there, cfdump is just smart enough to print out cfcomponent pages a little different.
@Elliott,
Ahhh, thanks. Much appreciated.
@Ben
No problem. Can't wait to see what you're cooking up :)
@Elliott,
Thanks for all your help - it worked like a charm:
www.bennadel.com/index.cfm?dax=blog:1212.view
ColdFusion's Evaluate() is Godlike. It is the secret sauce for dyamanic rules engine driven applications. We use it extensively... If you're looking to build a rules based decision engine, you'll need this tool in your arsenal.
@Alan,
In the past I have not been much of a fan of the Evaluate() method. I find that I can often get around it by using pointers and better struct notation. However, with something like this, I simply could not find a way around it.
I have not done much in the way of rules based dynamic goodness. Sounds very cool.
@Ben
Sometime I'll have to show you (or perhaps it'd be a worthwile article for other developers). A rules engine puts the capabilities of Cold Fusion in the business automation/workflow class of applications... and totally rocks. Plus there are a lot more things you can use it for too.
Our little company (and loanxengine product) just got selected as a preferred technology partner for LendingTree. We service a number of their lenders already, but LendingTree will be officially outsourcing automatic loan bidding and CRM processes to us!
Image, the next time someone does a loan through LendingTree, they may actually be using Cold Fusion. ;*)
Cool, no? ...And much of it thanks to Evaluate() Cold Fusion Rocks!!
Hello everybody,
I think my query is related to this topic of discussion. I'm really not sure who else to contact. I have a cfm template which I refer to in the <img> src tag (<img src="OutputImage.cfm" />) . It outputs the image correctly and as I would expect it to. Here is the code:
OutputImage.cfm
----------------
<cfscript>
Context = getPageContext();
Context.setFlushOutput(false);
Response = Context.getResponse().getResponse();
OutputStream = Response.getOutputStream();
Response.setContentType("image/jpeg");
OutputImage(session.sessionid,'en',OutputStream);
OutputStream.flush();
OutputStream.close();
</cfscript>
However, when I move the exact same code to a function in a CFC and call it, nothing but the image gets outputted in the browser. Any ideas why this is the case? Is there a different way to call the GetPageContext() method within a CFC?
Here is the call:
<img src="#request.RenderImage()#" />
Here is the same code in a cfc.
<cfcomponent displayname="ImageRenderer">
<cffunction name="RenderImage" returntype="void" access="public" output="true">
<cfscript>
Context = getPageContext();
Context.setFlushOutput(false);
Response = Context.getResponse().getResponse();
OutputStream = Response.getOutputStream();
Response.setContentType("image/jpeg");
OutputImage(session.sessionid,'en',OutputStream);
OutputStream.flush();
OutputStream.close();
</cfscript>
</cffunction>
</cfcomponent>
I would really appreciate your help.
Regards,
John