Considering Aliases For EncodeForHtml(), EncodeForHtmlAttribute(), And EncodeForUrl() In ColdFusion
Typically, when creating user defined functions (UDFs) in ColdFusion, I like to package my functions inside of a ColdFusion Component (CFC) which I can then pass around using inversion of control (IoC). Recently, however, I've been working on a CFWheels project in which the built-in encodeForHtml()
function has been aliased as a globally available UDF named e()
. And, I must say, it's been really nice not having to type out the whole function name. In fact, it's been so nice that I'm considering making it a global function in my personal projects.
Aside: I know that I've seen the
e()
notion defined in PHP code; so, this might be a more common pattern than I'm aware of.
Adobe ColdFusion doesn't have a native mechanism for creating global functions. But, it does have a native mechanism for mix-ins: the CFInclude
tag. When we define our custom functions inside a CFML template, we can then include that template into any page context—both .cfm
and .cfc
—in order to make those functions available to that context execution.
To demonstrate, I'm going to alias the three encoding functions that I use in almost every rendered CFML template:
encodeForHtml()
→e()
encodeForHtmlAttribute()
→ea()
encodeForUrl()
→eu()
These will be in a CFML file called globals.cfm
. For the most part, these are just aliases; but, I'm going to use the opportunity to make the encodeForHtml()
function a little more flexible:
<cfscript>
/**
* I encode the given values for HTML output. If there is more than one argument, a
* space is used to delimit the encoded values.
*/
private string function e( string value = "" ) {
// In order to avoid an unnecessary array allocation, check for the most common
// use-case of a single argument.
if ( arrayLen( arguments ) == 1 ) {
return encodeForHtml( arguments.value );
}
// The more flexible version will infix a space between each encoded argument.
return arrayToList(
arrayMap( arguments, ( value ) => encodeForHtml( value ) ),
" "
);
}
/**
* I encode the given value for HTML attribute output.
*/
private string function ea( string value = "" ) {
return encodeForHtmlAttribute( arguments.value );
}
/**
* I encode the given value for URL output.
*/
private string function eu( string value = "" ) {
return encodeForUrl( arguments.value );
}
</cfscript>
As you can see, ea()
and eu()
are just short-hand aliases for the underlying built-in functions. But, my e()
function is actually a variadic function that will encode N-arguments, including a space in between each encoded value. These functions can now be included into any page context:
<cfscript>
// Include (ie, mix-in) the global function aliases into this page context.
include "./globals.cfm";
contacts = [
{ id: 1, firstName: "Sarah", lastName: "Stubbs", role: "admin" },
{ id: 1, firstName: "Larry", lastName: "Lauroo", role: "manager" },
{ id: 1, firstName: "Hanah", lastName: "Horace", role: "manager" }
];
</cfscript>
<cfoutput>
<!--- Demonstrate e(), ea(), and eu() usage. --->
<ul>
<cfloop array="#contacts#" item="contact">
<li data-role="#ea( contact.role )#">
<a href="contact.cfm?id=#eu( contact.id )#">
#e( contact.firstName, contact.lastName )#
</a>
</li>
</cfloop>
</ul>
<!--- Compare to using the native built-in functions. --->
<ul>
<cfloop array="#contacts#" item="contact">
<li data-role="#encodeForHtmlAttribute( contact.role )#">
<a href="contact.cfm?id=#encodeForUrl( contact.id )#">
#encodeForHtml( contact.firstName )# #encodeForHtml( contact.lastName )#
</a>
</li>
</cfloop>
</ul>
</cfoutput>
The difference isn't massive; but, to me, the code using the aliases is clearly easier to read. The encoding function names are so long, especially the attribute one, that they really do add a lot of syntactic noise to the CFML markup. And, literally every template that I render uses at least one, if not all, of these three encoding functions.
A Slippery Slope of Trade-Offs
As I mentioned above, I usually use a ColdFusion component to package up my user defined functions. I love using a CFC because it forces me to prefix all of my method invocations with the variable that references said CFC. This makes the code very explicit; which, in turn, makes the code easy to understand and easy to debug.
When I start defining functions inside a CFML template that is then included into another page context, I lose this intuitive understanding of the code organization. Suddenly, it becomes unclear if the function I'm calling is a native function; or, if it's been included; and, if it has been included, from which template did it originate?
As such, I would treat these aliases as the exception to the rule. That is, I will continue using CFCs to package most of my user defined functions; but, I would make an exception for these alias functions that are used with such high volume that the resultant brevity of the code has a value in and of itself.
Want to use code from this post? Check out the license.
Reader Comments
It's worth mentioning that Lucee CFML actually has a native way to extend the global scope using extensions. Basically, you can define your own globally-available functions:
www.bennadel.com/blog/3682-installing-user-defined-functions-udf-as-an-extension-for-built-in-functions-bif-in-lucee-5-3-2-77.htm
We've done something similar with a component-based approach in a couple of cases and for similar reasons. With our approach, we'd instantiate the component with the method-based shorthand versions of the BIFs as "e" (for "encode") or "ef" (for "encodeFor") and have shorthand methods named "u", "h", "ha" along with all of the other "encodeFor...()" methods for completeness/consistency. That would give us "e.u()" and "e.h()", etc., calls throughout the code, eliminating most of the syntactic noise of the longer BIF names and still retaining the explicit aspect of where those functions are implemented.
@Ron,
Ahh, very nice. That's a good compromise! Still very short; but, also still very explicit! Seems like it could be the best of both worlds.
@Ron
I love that approach! I've found that included templates can make debugging/troubleshooting so much more difficult, especially when the code cannot be viewed all at once and requires lots of scrolling.
Yeah, having had some time to reflect on this conversation, I think Ron's approach is better for longer-term maintenance. Plus, by moving the
h()
,u()
, etc. functions into a CFC, you open the door to use dependency-injection (DI) inside that CFC as well. So not only does it keep things more consistent with other areas of the app, it might also make writing this stuff a bit easier.Post A Comment — ❤️ I'd Love To Hear From You! ❤️
Post a Comment →