Ask Ben: Creating ColdFusion Templates On The Fly

Posted September 21, 2009 at 9:42 AM by Ben Nadel

Tags: ColdFusion, Ask Ben

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

Sep 21, 2009 at 10:16 AM // reply »
14 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

:)


Sep 21, 2009 at 10:38 AM // reply »
4 Comments

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


Sep 21, 2009 at 10:39 AM // reply »
51 Comments

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


Sep 21, 2009 at 10:43 AM // reply »
11,238 Comments

@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.


Sep 21, 2009 at 5:28 PM // reply »
12 Comments

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


Sep 21, 2009 at 5:51 PM // reply »
47 Comments

XML and XSL work great together for generating cfm's and cfc's and practically any file you want.


Sep 21, 2009 at 5:58 PM // reply »
11,238 Comments

@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.


Sep 22, 2009 at 10:53 AM // reply »
10 Comments

In ColdFusion 9 this can be done easily by using VFS.

http://www.coldfusionjedi.com/index.cfm/2009/7/25/Very-simple-very-ugly-CMS-built-with-ColdFusion-9


Sep 22, 2009 at 10:55 AM // reply »
11,238 Comments

@Sumit,

If you're gonna plug Ray's post, then I'm gonna have to plug mine as well ;)

http://www.bennadel.com/blog/1650-Learning-ColdFusion-9-The-Virtual-File-System-RAM-Disk-.htm


Sep 22, 2009 at 11:09 AM // reply »
10 Comments

@Ben,

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


Oct 17, 2009 at 12:03 AM // reply »
1 Comments

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


Nov 13, 2010 at 7:51 AM // reply »
17 Comments

Thanks for this Ben. You saved my day. God bless you.


Jan 4, 2012 at 10:57 AM // reply »
1 Comments

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.



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 19, 2013 at 2:31 PM
My Experience With AngularJS - The Super-heroic JavaScript MVW Framework
It's funny really just how well that image describes the way I would imagine most people that go with angular for some project is. I have had a similar roller-coaster ride with it as well, but not qu ... read »
May 17, 2013 at 7:42 PM
HashKeyCopier - An AngularJS Utility Class For Merging Cached And Live Data
Ben - thanks so much for posting these Angular articles and findings, they've been a huge help towards learning one of the more 'complex' JavaScript frameworks out there (IMO). I have been using Angu ... read »
May 16, 2013 at 5:01 PM
UPDATE: Parsing CSV Data Files In ColdFusion With csvToArray()
Your code was the closest thing I've found to obtaining some direction for converting ISO fields to values that CF can translate properly. Thank you for posting! ... read »
May 15, 2013 at 10:37 PM
Very Simple Pusher And ColdFusion Powered Chat
hi id making plz easy ... read »
May 15, 2013 at 6:07 PM
Making SOAP Web Service Requests With ColdFusion And CFHTTP
Ben, you once again saved my bacon at work. Thank you, thank you, thank you! ... read »
May 15, 2013 at 4:15 PM
What If All User Interface (UI) Data Came In Reports?
@Josh, Thanks! @Ben, I definitely recommend the David West book "Object Thinking" I've been quoting from. It goes deeply into the philosophy and history of OO programming. His breadth ... read »
May 15, 2013 at 11:36 AM
Ask Ben: Print Part Of A Web Page With jQuery
I found this helpfull when you need to keep (refresh) the original parent page after closing the iframe child print dialog (Hoping you're not using a form at this time so it won't submit again): On ... read »
May 14, 2013 at 7:13 PM
What If All User Interface (UI) Data Came In Reports?
@Jonah, If there's any books you'd recommend on the subject of domain modelling, I'd love to hear it. I just downloaded the free PDF of "Domain Driven Design Quickly". Figured I'd give it ... read »
InVision App - Prototyping Made Beautiful With Prototyping Tools