It was quite a pain trying to find a decent template system to use in Coldfusion. I finally landed on Freemarker, because I had used it’s syntax before. I had to jump through some hurdles to finally figure out how to effectively use it, so here I will share how I did it.
Download JavaLoader:
http://javaloader.riaforge.org/
Download Freemarker:
http://freemarker.sourceforge.net/freemarkerdownload.html
I use JavaLoader because I do not always have control over the Coldfusion environment that my applications will run on.
Here is the cfc
<cfcomponent name="FreeMarker" output="no"> <cfscript> instance=StructNew(); instance.ID=CreateUUID(); instance.Out=createObject("java", "java.io.StringWriter").init(); instance.Map=StructNew(); instance.JavaLoader=""; </cfscript> <cffunction name="init" hint="Constructor" access="public" returntype="FreeMarker" output="false"> <cfargument name="JavaLoader" type="any" required="yes"> <cfscript> instance.JavaLoader=arguments.JavaLoader; </cfscript> <cfreturn this /> </cffunction> <cffunction name="setContent" access="public" returntype="FreeMarker" output="false"> <cfargument name="Template" type="string" required="yes"> <cfscript> instance.Content=createObject("java", "java.io.StringReader").init(arguments.Template); instance.Template=instance.JavaLoader.create("freemarker.template.Template").init("FMT_" & instance.ID, instance.Content); </cfscript> <cfreturn this /> </cffunction> <cffunction name="process" access="public" returntype="string" output="false"> <cfscript> instance.Template.process(instance.Map, instance.Out); instance.Out.flush(); </cfscript> <cfreturn instance.Out.toString() /> </cffunction> <cffunction name="putInt" access="public" returntype="FreeMarker" output="false"> <cfargument name="name" type="string" required="true" /> <cfargument name="in" type="numeric" required="true" /> <cfscript> instance.Map[arguments.name]=JavaCast("int", arguments.in); </cfscript> <cfreturn this /> </cffunction> <cffunction name="putString" access="public" returntype="FreeMarker" output="false"> <cfargument name="name" type="string" required="true" /> <cfargument name="in" type="string" required="true" /> <cfscript> instance.Map[arguments.name]=JavaCast("string", arguments.in); </cfscript> <cfreturn this /> </cffunction> <cffunction name="putArray" access="public" returntype="FreeMarker" output="false"> <cfargument name="name" type="string" required="true" /> <cfargument name="in" type="array" required="true" /> <cfscript> instance.Map[arguments.name]=arguments.in; </cfscript> <cfreturn this /> </cffunction> <cffunction name="putStruct" access="public" returntype="FreeMarker" output="false"> <cfargument name="name" type="string" required="true" /> <cfargument name="in" type="struct" required="true" /> <cfscript> instance.Map[arguments.name]=arguments.in; </cfscript> <cfreturn this /> </cffunction> <cffunction name="putQuery" access="public" returntype="FreeMarker" output="false"> <cfargument name="name" type="string" required="true" /> <cfargument name="in" type="query" required="true" /> <cfscript> instance.Map[arguments.name]=QueryToArray(arguments.in); </cfscript> <cfreturn this /> </cffunction> <cffunction name="getMap" access="public" returntype="struct" output="false"> <cfreturn instance.Map /> </cffunction> <!--- ===================================== ---> <!--- Got from Ben Nadel: http://www.bennadel.com/blog/124-Ask-Ben-Converting-a-Query-to-an-Array.htm ---> <cffunction name="QueryToArray" access="private" returntype="array" output="false" hint="This turns a query into an array of structures."> <!--- Define arguments. ---> <cfargument name="Data" type="query" required="yes" /> <cfscript> // Define the local scope. var LOCAL = StructNew(); // Get the column names as an array. LOCAL.Columns = ListToArray( ARGUMENTS.Data.ColumnList ); // Create an array that will hold the query equivalent. LOCAL.QueryArray = ArrayNew( 1 ); // Loop over the query. for (LOCAL.RowIndex = 1 ; LOCAL.RowIndex LTE ARGUMENTS.Data.RecordCount ; LOCAL.RowIndex = (LOCAL.RowIndex + 1)){ // Create a row structure. LOCAL.Row = StructNew(); // Loop over the columns in this row. for (LOCAL.ColumnIndex = 1 ; LOCAL.ColumnIndex LTE ArrayLen( LOCAL.Columns ) ; LOCAL.ColumnIndex = (LOCAL.ColumnIndex + 1)){ // Get a reference to the query column. LOCAL.ColumnName = LOCAL.Columns[ LOCAL.ColumnIndex ]; // Store the query cell value into the struct by key. LOCAL.Row[ LOCAL.ColumnName ] = ARGUMENTS.Data[ LOCAL.ColumnName ][ LOCAL.RowIndex ]; } // Add the structure to the query array. ArrayAppend( LOCAL.QueryArray, LOCAL.Row ); } // Return the array equivalent. return( LOCAL.QueryArray ); </cfscript> </cffunction> </cfcomponent>
Hi,
thanks for the cfc. It works fine but when trying to include a template with the error “Error reading included file /inc.ftl” appears. I tried nearly every path combination, but it doesn’t work.
Have you had this problem, too? Any idea what’s wrong?
Best regards,
Michi
I actually don’t use the template including features, so I am not sure. I believe that you may have to edit the cfc to add a function to include a template path to the Freemarker object to tell it where to look for templates.
Sorry for the very late response, I always forget that people may see what I post.
Thanks Tyler for creating this. Here’s how to set the path for including templates (note that you don’t want to include CF templates since includes do not get processed by CF)
instance.Content=createObject(“java”, “java.io.StringReader”).init(arguments.Template);
instance.Configuration=createObject(“java”,”freemarker.template.Configuration”).init();
instance.ConfigDir = createObject(“java”,”java.io.File”).init(“E:\wwwroot\Test\”);
instance.Configuration.setDirectoryForTemplateLoading(instance.ConfigDir);
instance.Template=createObject(“java”,”freemarker.template.Template”).init(“FMT_” & instance.ID, instance.Content, instance.Configuration);
Thanks kc, that looks good.