Skip to main content
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Jeff McDowell and Joel Hill
Ben Nadel at cf.Objective() 2014 (Bloomington, MN) with: Jeff McDowell ( @jeff_s_mcdowell ) Joel Hill ( @Jiggidyuo )

Using CFModule To Render Templates With Isolation In ColdFusion

By on
Tags:

ColdFusion custom tags are often discussed in terms of creating reusable chunks of code. For example, I make heavy use of custom tags in my HTML Email DSL. But, custom tags can serve a much more mundane purpose: they can be used to render a single-use template with strong isolation boundaries. Meaning, they can be used to give each template its own, unique variables scope and page context.

To see what I mean, let's create a simple ColdFusion template that defines a variable and then writes it to the response:

<cfscript>

	context = "template";

	writeOutput( "<p> Template context: #context# </p>" );

</cfscript>

Since the context variable is unscoped, it's implicitly being written to the variables scope. By default, every template shares the variables scope of the calling context. Which means, if the parent page has a context variable, the assignment in the above template will overwrite it.

To demonstrate this behavior, let's create another ColdFusion page that uses the CFInclude tag to render the above template:

<cfscript>

	context = "page";

	// Execute template as plain include.
	include "./my-template.cfm";

	writeOutput( "<p> Page context: #context# </p>" );

</cfscript>

Note that the parent page is defining its own context variable before it includes the given template. And, since the CFInclude tag uses the same page context, we get the following output when we run this ColdFusion page:

Template context: template

Page context: template

As you can see, the template value, assigned within the included template, overwrote the context variable in the parent page. In other words, the context variable in both context collided.

To prevent this collision, we can use the CFModule tag instead of the CFInclude tag. Both tags take a template path; but, the CFModule tag executes the given template as a ColdFusion custom tag. Which means, it receives its own variables scope and page context.

Let's try running the above code, replacing CFInclude with CFModule:

<cfscript>

	context = "page";

	// Execute template as isolated module.
	cfmodule( template = "./my-template.cfm" );

	writeOutput( "<p> Page context: #context# </p>" );

</cfscript>

This time, when we run this ColdFusion code, we get the following output:

Template context: template

Page context: page

This time, the context assignment within the "included" template did not overwrite the existing context variable in the parent page. This is because the parent page and the included template were both executing in their own, isolated page context with their own isolated variables scope.

Without changing any of the logic within the included template, we're able to use the CFModule tag to completely change the way in which the template is executed. This allows us to create a strong boundary around the template that provides assurances about variables; and forces cross-template communication to be more explicit (either through custom tags mechanics or global scopes).

Want to use code from this post? Check out the license.

Reader Comments

24 Comments

Interesting. SORT of like the "args" scope that Coldbox provides when using renderView(view: "/views/view.cfm", args: {val: 1}). The args are only available to that view. But it is definitely NOT a protected context like the one CF offers through CFModule.

I wonder, though, how would CFModule be better than a component.function() with it's arguments and local scope. Those are also protected.

For any personal code I do these days, I try to ensure that it's both ACF and Lucee compatible. I run Lucee for my own webserver, but using Commandbox I can easily spin up a local server under either flavor. I don't think I've ever tried CFModule under either! 🙃

15,688 Comments

@Will,

I think with frameworks that render Views inside components (which I think is how it's mostly done these days), there's likely very little need to use cfmodule. In my particular case, it's all nested switch/include statements. So, without cfmodule, it's just variables all the way down 😆 Essentially, I'm trying to add the type of encapsulation that the component approach gives you; but without having to fully refactor all the code.

The cfmodule() approach is definitely fine in both Adobe ColdFusion and Lucee CFML.

15,688 Comments

One other thing that might be worth mentioning is that you can use this technique from with the Application onRequest() event handler as well. The onRequest() event handler gives you a chance to override which ColdFusion script is being executed for a given request. Normally, it might be coded like this:

function onRequest( required string scriptName ) {
	include scriptName;
}

What this does it execute the given template, scriptName; but, do so as a "mixin" to the Application.cfc file. This inherently gives the scriptName file access to all of the variables inside the application component. Which may (or mayb not) be what you intended to do.

If you want to create more encapsulation, you can use the CFModule tag:

function onRequest( required string scriptName ) {
	cfmodule( template = scriptName );
}

This still invokes the given template / script; but, it will encapsulate it in its own page context thereby preventing the script from reaching up into the Application.cfc variables.

20 Comments

Great post Ben! The encapsulation is a great benefit of using cfmodule instead of cfinclude as you point out. I don't see too many people use of cfmodule though. I remember back in the early CF days when getTickCount based performance tips were popular that people said using cfmodule was slower than cfinclude.

I suppose this make sense since it is creating a new context to execute in, but it might not be significant. I'm hoping I gave you an idea for a future post here 😀

15,688 Comments

@Pete,

I still have a warm place in my heart for custom tags. It's one of the very few presentations I've ever given. There's almost certainly some overhead; but, I have to imagine that it is next-to-nothing when you only use a few per request. That said, you now have me curious to poke at the problem a bit!

A few years ago, I did run up against a strange Lucee CFML bug in which the cfimport tag causes Lucee to bypass the file cache when checking to see if the custom tag exists:

I don't know if this has been fixed in Lucee 6.x. This was causing me some concern inside a Docker container on Mac (where file IO on a mounted volume is terrible). But, the performance ended up not being a problem once in production (in a container-native environment).

I'll report back when I've noodled on it a bit more.

Post A Comment — I'd Love To Hear From You!

Post a Comment

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel