Skip to main content
Ben Nadel
On User Experience (UX) Design, JavaScript, ColdFusion, Node.js, Life, and Love.

Tag Islands And CFScript-Based Tags Bring Perfection To ColdFusion In Lucee CFML 5.3.4.80

By Ben Nadel on
Tags: ColdFusion

In February of this year, I wrote two back-to-back posts about fascinating features of Lucee CFML. First, that you could use all ColdFusion tags in CFScript using a consistent syntax (thanks Gert Franz); and, second, that Lucee CFML supports Tag Islands within CFScript (thanks Andrew Kretzer). In the month-and-half since those posts, I've come to believe that these two features truly round-out the ColdFusion feature set in a fundamental way, bringing an elegant perfection to the language. And, I'm freaking out that people aren't freaking out over it!

NOTE: I wrote my demo using CommandBox and Lucee CFML 5.3.4.80 (hence the version in the title of my post); however, I believe that tag islands were introduced in Lucee CFML 5.3.1.13.

To be clear, I am not saying that Lucee CFML is flawless. Just like any other programming language, it has bugs and performance issues. But, I do feel in my core that the aforementioned features close the loop on the CFScript-based syntax in such a way that makes CFScript feel, well, perfect.

As I've started to incorporate these two features into my ColdFusion research and development, I feel like all previous impediments have been removed; I feel like I just dropped the last Infinity Stone into my own personal Infinity Gauntlet.

Thanos inserting the last Infinity Stone into his Infinity Gauntlet.

If you haven't read my previous two posts, here's the quick rundown:

  • In Lucee CFML, all ColdFusion tags can be used within CFScript by dropping the cf prefix (ex, cfloop becomes loop) and using curly-braces to demarcate the tag body.

  • In Lucee CFML, you can seamlessly switch back and forth between CFScript syntax and tag-based syntax by wrapping a block of code in triple-back-ticks.

To see this in action, here's a quick demo in which I query for blog posts using the CFQuery and then output links to the matching posts:

<cfscript>

	param name = "url.filter" type = "string" default = "";

	// If no filter was provided, exit out of page processing.
	if ( ! url.filter.len() ) {

		echo( "Please provide a filter." );
		exit;

	}

	// If we've made it this far, we have a non-empty filter - find matching posts.
	// --
	// NOTE: The following feature demonstrates using TAG ISLANDS within CFScript, which
	// allows us to use the full power and brevity of tags like CFQuery and CFSaveContent
	// right from within the sleek, minimal CFScript syntax.
	```
	<cfquery name="posts" datasource="bennadel">
		SELECT
			e.id,
			e.name
		FROM
			blog_entry e
		WHERE
			e.name LIKE <cfqueryparam value="%#url.filter#%" sqltype="varchar" />
		AND
			e.isActive = 1
		ORDER BY
			e.id DESC
	</cfquery>
	```

	// Rather than calculating the URL during the output, let's just add a new column to
	// the query and add the URL here. This will make the output logic much cleaner.
	posts.addColumn( "url", "varchar" );

	// Generate the URL for each post.
	// --
	// NOTE: The following demonstrates using TAGS within CFScript by dropping the "cf"
	// prefix (ex, "cfloop" to "loop") and then using curly-braces for the tag-body.
	loop query = posts {

		baseFilename = posts.name
			.lcase()
			.reReplace( "['""]", "", "all" ) // Strip out quotes.
			.reReplace( "[^a-z0-9]", "-", "all" ) // Replace non-base characters.
			.reReplace( "-{2,}", "-", "all" ) // Replace repeated dashes.
			.reReplace( "^-+|-+$", "", "all" ) // Strip out leading or trailing dashes.
		;

		posts.url = "www.bennadel.com/blog/#posts.id#-#baseFilename#.htm";

	}

	// Output matching posts response.
	if ( posts.recordCount ) {

		```
		<cfoutput>

			<h1>
				#numberFormat( posts.recordCount )# Matching Posts
			</h1>

			<p>
				Filter: <code>#encodeForHtml( url.filter )#</code>
			</p>

			<ol>
				<cfloop query="posts">
					<li>
						<a
							href="#encodeForHtmlAttribute( posts.url )#"
							target="bennadel"
							style="color: ##ff3366 ;">
							#encodeForHtml( posts.name )#
						</a>
					</li>
				</cfloop>
			</ol>

		</cfoutput>
		```

	// Output the "no posts" response.
	} else {

		```
		<cfoutput>

			<h1>
				Sorry, No Posts Found
			</h1>

			<p>
				Your query for <code>#encodeForHtml( url.filter )#</code> did not match
				any active blog posts.
			</p>

		</cfoutput>
		```

	}

</cfscript>

From a logic standpoint, there's nothing terribly interesting about this demo - the magic is all in the syntax. Some things to note:

  • I'm using the CFQuery tag right inside CFScript. This allows us to leverage all of the elegance, power, and developer-ergonomics of the CFQuery tag, complete with its protection against SQL-injection attacks, without having to create tag-based components or deal with the verbosity of queryExecute().

  • I'm looping over the Query object using a query loop (as opposed to a for-in loop which incurs the overhead of generating a ColdFusion Struct for each loop iteration).

  • I'm outputting HTML tags right from within my CFScript without having to deal with messy echo() or writeOutput() calls.

And, when we run the above Lucee CFML code, we get the following browser output:

HTML output using tag-islands and CFScript-based tags in Lucee CFML.

I'm a little bit freaking out over how awesome this is! I wanna run around and start flipping some tables! These two features feel like a true mic drop moment for Lucee CFML.

The only downside to this is that my IDE (SublimeText 3) doesn't love the tag islands. It does the color-coding right (as long as I don't use any apostrophes); but, every time I go to close an HTML / CFML tag inside a tag island, it automatically inserts a </cfscript> tag. This is frustrating; but, it's an obstacle that I will gladly endure if it means that I get use the indisputable elegance of such a robust CFScript feature-set.

I am truly giddy over this. It reminds me of a quote from Muscle: Confessions Of An Unlikely Bodybuilder:

Iron made sense to no one. To no one, that is, but me. All I knew was that I had found a sanctuary in the gym, and the more I trained, the better I felt. Out on the streets of New York, I'd found nothing but impediments, red lights, and stop signs everywhere. Inside the gym, I saw only green.

.... from exercise to exercise I'd go, feeling as if I were driving a car on a dark, wet night in the city. Suddenly, the stoplight just ahead turns green, the next one green, and green again. You don't need to brake for even one light. All you see is the road before you. You're not quite sure why, but you're going at the right speed at the right place and time. You take a quick look at the speedometer. Just to memorize the reading. But there's no need. Just keep it going, another light, another block, another weight, another exercise. Green, green, green. (Page 61)

That's what CFScript feels like now in Lucee CFML: no impediments, just open road and green lights. The developer ergonomics of these two features are unlike anything I've seen elsewhere (though, to be fair, my exposure to other languages is quite limited). I'm just so thrilled that I get to come to work every day and write ColdFusion code.



Reader Comments

Hi Ben, I absolutely agree with you! Love these features.
If only our editors (mine is IntelliJ) would pick this up, it would be at 100%. Currently, while my reasoning says it's perfect, my editor is big-time complaining about errors in my code.

By the way, at CFCamp2019, direct Java code support in cfm/cfc files was demo'd by Michael Offner, as a feature for Lucee6.

Hope you're well, and keep those blogs coming 👍👍👍
Paul

Reply to this Comment

@Paul,

Yeah, SublimeText sounds a little better; but, still lost of frustrations. Some of it has nothing to do with ColdFusion itself. For example, even when I am in TypeScript, and I try to use an apostrophe within block of back-ticks, all the text in the rest of my document turns yellow. Same for when I am in Tag Island.

I'm super keen on learning more about native Java support in Lucee - that sounds amazing.

Reply to this Comment

@Chris,

I still need to get our prod app on the latest Lucee. We're still on 5.2.x, so I haven't been able to start using this in my day-to-day work yet (just R&D). I need this !!! :D

Reply to this Comment

Ben. I am fairly sure that the CF prefix removal feature has been around for some time?

But, the back tick version with full tag support is definitely new and extremely awesome!

Reply to this Comment

@Charles,

You are probably right. Since I'm fairly new to the Lucee game, I don't have a good sense of how long features have been around. That's actually one thing that I really miss in the document - the history of features. In the Adobe ColdFusion docs, there is often some sort of "history" comment about when a feature was added and/or changed.

For example, if you look at the Adobe ColdFusion docs for cfcookie, you can see:

ColdFusion 10: Added the preserveCase and encodeValue attributes.

ColdFusion MX 6.1:

  • Changed the expires attribute: it now accepts a date time object.
  • Cookie names can include all ASCII characters except commas, semicolons, or whitespace characters.

ColdFusion 9: Added the attribute httponly.

I always founds this really helpful, especially when I wasn't sure why a feature wasn't working the way I thought it should.

Of course, I know the Lucee team is much smaller (which is part of why they are so fast at getting things done); so, it's completely understandable that the docs wouldn't be as comprehensive.

Reply to this Comment

Actually, I think you told me about this, a few years ago?

That you can take any tag and remove the CF prefix and use it in CFSCRIPT. I must say this little feature can be a real life saver, when you don't have access to reference documentation. It is so easy to remember. I have used it once or twice with looping stuff.

I am one of those strange people who like cfloop list:

<cfloop list="" index="">
</cfloop>

In CFScript, I don't like being forced to turn the list into an array, just loop over the list. This seems kind of counter intuitive.

So, this is a real little gem!

Reply to this Comment

This cfscript tag island feature is SUPER awesome! Wow!!

I've been 100% tag based for so long, but I've recently been working to convert my components over to script based, with some help from http://cfscript.me/ (which is also super awesome)

But I can't stand building queries as strings, <cfquery> is still the best, but this kept my components in an uncomfortable state where some functions were tag based and some were script. This tag island solution is an awesome fix!

vscode highlighting is a little bit off, sounds similar to sublime, it's bearable but hopefully it can be improved.

I super appreciate you bringing this to the public's attention, Lucee org needs to highlight these awesome hidden features to us all.

Reply to this Comment

@Charles,

It's funny you mention that, re: loop, I've been using loop a lot lately, especially for Query objects. Much of the time, I am generating a JSON response based on a query; but, I don't use all the query data. So, I figure, "why convert the query to an array-of-structs just so I can then generate another array-of-structs for the response". Not only a query-loop super fast, it doesn't have to generate any intermediary data.

Normally, I am against "micro optimizations" (not against, just I don't care that much). But, in cases where I'm on a hot code-path, I feel like little tweaks like that can make a difference.

Reply to this Comment

@Jordan,

I'm glad you're finding this as mind-blowing as I am :D This was the missing key. I feel like Lucee CFML + CFScript are now unstoppable!!

Reply to this Comment

Post A Comment

You — Get Out Of My Dreams, Get Into My Blog
Live in the Now
Oops!
NEW: Some basic markdown formatting is now supported: bold, italic, blockquotes, lists, fenced code-blocks. Read more about markdown syntax »
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.