Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.
I am the chief technical officer at InVision App, Inc - a prototyping and collaboration platform for designers, built by designers. I also rock out in JavaScript and ColdFusion 24x7.
Meanwhile on Twitter
Loading latest tweet...
Ben Nadel at Scotch On The Rock (SOTR) 2010 (Amsterdam) with: Terrence Ryan and David Huselid and Alison Huselid and Claude Englebert

Ask Ben: Creating ColdFusion Templates On The Fly

By Ben Nadel on

Hi Ben, frequent reader, first time writer. I'd be surprised if this hasn't been asked yet, but can't seem to find the answer anywhere. I'm trying to figure out a way to allow an application to build basic Coldfusion pages on the fly. i.e. this is a simple CMS that I'm building for someone, where actual page templates are 10 lines long and have a few CF tags and CF includes in them. Ideally, I'd like to allow the files to be created as soon as they name them, rather than (currently) having to physically create the pages. I have tried cffile and cfsavecontent, but it tried to process the Coldfusion tags, rather than include them in the created cfm file like I'd like. Have you come across anything like this? Thanks!

When it comes to creating ColdFusion code that writes ColdFusion code, the biggest trip-up, as you are seeing, is that your newly written ColdFusion code has a tendency to want to execute rather than being stored as text. In my experience, there are two ways to get around this: One is to work from a template into which you replace variable values; and Two, is to escape your ColdFusion code as you craft it, then unescape it before you write it to disk. Each of these methods has a use case, so I wouldn't say that one is better than the other. In general, picking a methodology comes down to how "templatable" your code is.

For these demos, I am going to keep it extremely simple; we are going to create a ColdFusion file that sets a name value and then includes a display file which outputs the name in a very "Hello World" type way.

Let's explore the templating technique first. In this approach, we are going to start off with an existing ColdFusion file that has place holders for the values we want to set. In this example, I created a "template.cfm" to hold our root template:

template.cfm

  • <!--- Set the person's name. --->
  • <cfset name = "$$name$$" />
  •  
  • <!--- Include the display file. --->
  • <cfinclude template="./display.cfm" />

As you can see, this ColdFusion file has a place holder for our name variable. This place holder is defined by "$$name$$". Now that we have this, we can read in the file contents, replace the target variables, and write the new code back to disk:

  • <!---
  • Read in the template file as a text file.
  •  
  • NOTE: You do not want to CFInclude thie file as that will
  • evaluate the ColdFusion tags in the process.
  • --->
  • <cfset personTemplate = fileRead(
  • expandPath( "./template.cfm" )
  • ) />
  •  
  • <!---
  • Now that we have the person's display template as a text
  • file, we want to replace out the variables with our known
  • values. In our case, we are simply going to replace out
  • the variable $$name$$ with the appropriate value.
  • --->
  • <cfset personTemplate = replace(
  • personTemplate,
  • "$$name$$",
  • "Molly",
  • "all"
  • ) />
  •  
  • <!---
  • Now that have our template updated with the appropriate
  • names, we can write the new ColdFusion code to a physical
  • file where it can be used.
  • --->
  • <cfset fileWrite(
  • expandPath( "./molly.cfm" ),
  • personTemplate
  • ) />

Here, we are reading in the template file contents and replacing the variable "$$name$$" with the value "Molly." Then, we write the new code back to the disk with at the file "molly.cfm". When we open up this file, we see:

molly.cfm

  • <!--- Set the person's name. --->
  • <cfset name = "Molly" />
  •  
  • <!--- Include the display file. --->
  • <cfinclude template="./display.cfm" />

As you can see, our value "Molly" was replaced into the templated content in the appropriate place.

The template technique works really well if the code you are outputting is fairly static. If you really need to create some dynamic code, however, then we need to take a slightly more complex approach in which we actually define the future code using ColdFusion code. At this point, we hit the problem you were facing above - the ColdFusion code we try to output gets executed rather than saved for later. To cope with this problem, we need to escape the ColdFusion code that we want to execute at a later time. To keep this as readable as possible, I use the following escape combinations:

  • #val# becomes <%=val=%>
  • < becomes <%
  • > becomes %>

Not only does this prevent the escaped ColdFusion code from executing, in my particular editor (HomeSite), code that has <% .. %> notation is highlighted in yellow (it thinks it is ASP / PHP code I believe). This makes it extremely clear which code is your "here and now" code and which code is your "future" code.

In this demo, rather than reading in the "template.cfm" code base, we are going to create it from scratch using CFSaveContent and escaped ColdFusion code:

  • <!---
  • Store the variable values that we want to put into our
  • new ColdFusion template.
  • --->
  • <cfset name = "Tricia" />
  •  
  • <!---
  • Create the new content for our ColdFusion page. Because we
  • want to WRITE ColdFusion code and NOT EXECUTE ColdFusion
  • code, we need to escape our CF Tags. So, in the following
  • content, our ColdFusion tags will look more like ASP tags.
  •  
  • NOTE: Notice that the # marks below ARE evaluated in order
  • to complete the template.
  • --->
  • <cfsavecontent variable="templateCode">
  • <cfoutput>
  •  
  • <%!--- Set the person's name. ---%>
  • <%cfset name = "#name#" /%>
  •  
  • <%!--- Include the display file. ---%>
  • <%cfinclude template="./display.cfm" /%>
  •  
  • </cfoutput>
  • </cfsavecontent>
  •  
  • <!---
  • Now that we have our new ColdFusion code in memory, we need
  • to write it to disk. But, before we do that, we need to
  • unescape the code so that it will propertly execute later on.
  • --->
  •  
  • <!---
  • While I didn't have any escaped HASH marks in the current
  • code, I would normally escape them with something like:
  •  
  • <%=CF_VARIABLE=%>
  •  
  • ... which we need to turn into ...
  •  
  • #CF_VARIABLE#
  •  
  • As such, let's unescape these to single hash values. I am
  • going to these ones first so they don't interfear with the
  • other escaped ColdFusion tags.
  • --->
  • <cfset templateCode = reReplace(
  • templateCode,
  • "<%=|=%>",
  • "##",
  • "all"
  • ) />
  •  
  • <!--- UNEscape opening bracket. --->
  • <cfset templateCode = replace(
  • templateCode,
  • "<%",
  • "<",
  • "all"
  • ) />
  •  
  • <!--- UNEscape closing bracket. --->
  • <cfset templateCode = replace(
  • templateCode,
  • "%>",
  • ">",
  • "all"
  • ) />
  •  
  • <!---
  • Now that our code is fully UNEscaped, we can write it
  • out to the disk.
  • --->
  • <cfset fileWrite(
  • expandPath( "./tricia.cfm" ),
  • templateCode
  • ) />

As you can see, this is a bit more work, but it gives us a lot more flexibility in the code we need to generate. Because we are escaping the ColdFusion code within the CFSaveContent buffer, it does not execute right away. But, that is not to say that standard ColdFusion code in the CFSaveContent buffer will not execute; if you look at my example, you'll notice that the hash marks we left in (#name#) did execute right away, putting the value "Tricia" into our code buffer value.

When we run this code, we produce the following template, tricia.cfm:

tricia.cfm

  •  
  • <!--- Set the person's name. --->
  • <cfset name = "Tricia" />
  •  
  • <!--- Include the display file. --->
  • <cfinclude template="./display.cfm" />
  •  

One thing you'll notice here is that all the tabbing in the code creation file gets left in the generated code file. You can, of course, start to tweak the tabbing and line breaks in the code that generates your template; but, at that point, you have to ask yourself where the readability is most important: in the file you generated? Or, in the file that generated the code? That's not a rhetorical question - it really depends on how you are going to use the generated file. If it is a file that will be updated manually after generation, then readability in the generated file is most important; however, if it is never touched again, then I would say readability in the generator is the most useful.

All code generation that I have ever seen operates off of one (of both) of these two techniques. Like I said above, each is good in its own way and selecting a technique really depends on how static your code can be. The more static it is, the easier a template is to work with; the more dynamic it has to be, the more you will have to mix escaped and unescaped ColdFusion code files. I hope this helps!



Reader Comments

@Ben a method I've found for doing a similar thing is to Evaluate and then immediately DE the content.

So for example:

<cfset name = "Tricia" />
<cfset templateCode=Evaluate(DE('<cfset name = "#name#" />
<cfinclude template="./display.cfm" />'))>
<cfset fileWrite("#uploadpath#/test.cfm" ,templateCode) />

This avoids the necessity for finding and replacing escaped var's

:)

Reply to this Comment

Thanks for the post Ben! This is definitely a big help, and I'll look into it. I was able to also achieve what I wanted using Java to write out the file. Trevor's blog post her was a big help: http://www.burnette.us/blog/index.cfm/2006/1/30/Using-Java-Instead-of-cffile-to-Write-to-Disk

Reply to this Comment

I just went through this exact same thing. I think this is where reMatch really shines nicely. No matter what your variable holder is (mine was {var}) you can use a regular expression to return an array of all your "variables" in the document. If your variables are named the same as your replacement variables your replace function can be pretty easy. Here is a quick write up on reMatch and how I was using it.

http://www.danvega.org/blog/index.cfm/2009/9/17/reMatch-returns-an-array-of-matches

Reply to this Comment

@Tom, @Steve,

While the use of string concatenation works for delayed evaluation of the code you generate, I'd be cautious about using it as it does not lend itself well to readability / maintainability; that is the beauty of using CFSaveContent - very readable.

@Dan,

Word up - regular expressions are awesome for this kind of thing.

Reply to this Comment

Based on the original question it seems that Steve only needs to create CF templates so that pages from CMS can have proper URLs.

I would suggest to use mod_rewrite or similar approach to rewrite pretty much any URLs in the site to a page handler which can be traditional CF page. This is much cleaner specially when moving site from server to server (e.g. testing to production).

Also I think it's necessary to point out that writing CF templates on the fly can be a massive security whole. If for example on this code:

<cfset name = "$$name$$" >

value is taken directly from the client then malicious user could set it to something like

nothing"> <cfquery>drop everything</cfquery><cfset name="still nothing

I guess You get the point...

Hope this helps.

Tero

Reply to this Comment

@Tero,

You make some excellent points. In the long run, using some URL rewriting would probably be the best way to go, especially if you ever need to change up your strategy.

As far as a security hole, that would only be if the data written to the template was user-data; while that is possible, the askee gave me some code samples that they were working on and the data being written was more along the lines of a database-generated ID.

But, most excellent points!

@Hatem,

My only concern with XSLT is that it's not friendly :) I've used it a bunch, but it just never is fun. Plus, I find the output control to be very tedious.

Reply to this Comment

@Ben,

hahaha. I think I missed your post on that. But, I should have known better to think you didn't write on that.

Reply to this Comment

@Ben,
Thanks for this...really helped with an issue I was having with static cfm pages and changing navigation menus.

Reply to this Comment

Hey Ben!

Just want to thank you for all the hard work you put into your blog. It is a great resource for the CF community.

Happy new Years.

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Comments
Live in the Now
Oops!
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.