This question came in this morning, and while I have about two hundred questions in front of it, it was so interesting I had to cover it immidiately. Dave asks:
I have seen CMS applications in PHP that have templates that use variable for placing content, menu, footer, etc. Is there a way that such variables can be created using ColdFusion?
Absolutely! Let's look at a very simple example of this. Do note that there are multiple ways of solving this problem, and this is just one way. First, let's build some sample data:
<cfset data = queryNew("id,name,age")>
<cfloop index="x" from="1" to="10">
<cfset queryAddRow(data)>
<cfset querySetCell(data,"id",x)>
<cfset querySetCell(data,"name","User #x#")>
<cfset querySetCell(data,"age",randRange(20,90))>
</cfloop>
This code just creates a ten row query with some random data in it. Now let's create the template. For my template system, I'm going to pick an arbitrary token for replacement. In this case, tokens will be marked with the % character:
<cfsavecontent variable="template">
Hi, my name is %name% and I am %age% year(s) old.
</cfsavecontent>
As you can see, I've set up two tokens in this template, name and age. These tokens will correspond with the values in my query. Now let's show how we use the tokens:
<!--- tokens to look for are my columns --->
<cfset tokenList = data.columnList>
<cfloop query="data">
<!--- copy template --->
<cfset thisTemplate = template>
<cfloop index="token" list="#tokenList#">
<cfset thisTemplate = replaceNoCase(thisTemplate, "%" & token & "%", data[token][currentRow], "all")>
</cfloop>
<cfoutput>
#thisTemplate#
<hr>
</cfoutput>
</cfloop>
We begin by creating a token list. This is a list of tokens that we will look for in our template. We get our list based on the query. Now, we could have done this the reverse way. I could have looked at the template and found all the instances of %something%. In my case I took the easy way out and just used the columnlist variable. Which one is better? Well if I had scanned for %something% strings, I would have a list of every token used in the template. If for some reason a token was used that did not exist in the query, I could throw an error. My method will simply ignore those variables though.
Next we loop over our query. I create a copy of the template inside the loop. Then I look for each token in my token list. The token will just be the word, like "name" or "age", so in my replaceNoCase call, I add % to the beginning and end of the token. I then replace it with the value from the query.
Pretty simple. We use something a bit like this in our mail tool at Mindseye. We allow for certain tokens that can be placed in the email body. These tokens are replaced with the values from our mailing list data. This allows for more personalized emails. Again - there are many different ways you could handle this.
Archived Comments
This is great! I can't wait to try it out. Thanks Ray.
One significant advantage of the "parse out the tokens" method is that you can embed options in the tokens. For example, your %name% token could take an optional param for indicating whether the first, last, or full name should be inserted (i.e. %name=full%). You can do the same thing with three tokens (firstname, lastname, and fullname), but as you get more options for your tokens, that quickly becomes unweildly.
In one of our content management tools, we have an abstract token parser that pulls out ${command;opt:value;opt:value;...} type tokens. You register command listeners with the parser, and when a command is found the parser looks for a listener that can handle the command, and passes off processing to it. Certainly more complex, but very powerful as it lets you do dynamic processing to create the "injected" content.
Neat approach Barney! FYI, for those who want a way to get tokens like that, you could use the reGetAll udf from cflib. There are other ways as well.
I'm not finding a reGetAll UDF on cflib...
Listen to what I mean, not what I say... reFindAll. :)
An additional tip, and a different approach.
Tip: One additional reason to use Ray's approach and loop over the token list is security. If you loop over the tokens and try to process them, someone could slip something like this into a page:
"Let's see what we have here: %email.getClass()%"
The result would be relatively harmless, and you can code around it, but you get the idea. Sticking to a known set of tokens is just safer.
Different approach:
I'll just throw this into the mix... don't forget about namespaced custom tags. They're not for everyone, but it's worth considering.
JournURL's template scheme is simple, but does the job of a micro-programming language... flow control, variable setting, looping. It uses a mix of parsed tokens and custom tags, but the tokens stick to the format of a tag. In other words, in this example:
[weblog:entries]
[h1][weblog:title filter="xml"][/h1]
[p][weblog:body excerpt="255"][/p]
[div][weblog:date format="rfc822"][/div]
[/weblog:entries]
...the title, body, and date tags are actually custom tags, while "entries" is a token that's parsed via regex. The fact that they all work like normal tags makes things easier on the end-user, and I get some added flexibility in organizing code.
Of course, in this scenario, the security concern I outlined above becomes even more significant. I pre-scan every template page for issues before parsing out the tokens and then feeding the leftovers to CF. Be careful out there.
P.S. Ray... could you add a notice to the blog.cfc comment box, telling commenters if/how they need to escape markup? Every system is different, so it's handy to have a one-sentence description available.
For those about to java, Apache commons Jelly works perfectly with CF:
http://jakarta.apache.org/c...
With the new CFCProxy i suppose you could write tags in CF and execute them in Jelly as well.
I'm getting dizzy....
Roger- will add it to my todo list for 4.0. If I forget, slap me around a bit.