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:
<!--- 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:
<!--- 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:
<!--- 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!
Want to use code from this post? Check out the license.