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

Exploring Tag Islands (Tags In CFScript) In Lucee CFML 5.3.1.13

By Ben Nadel on
Tags: ColdFusion

Yesterday, in my post about using CFML tag syntax in CFScript, Andrew Kretzer told me that Lucee CFML now offers something called "Tag Islands" (seemingly as of Lucee CFML 5.3.1.13). I had never heard of Tag Islands before; and, other than this rather heated post on the Lucee Dev Forum, there's not really any documentation on the topic. As such, I wanted to take a few minutes and do a quick exploration of these Tag Islands so I could see how they work in Lucee CFML 5.3.1.13.

A Tag Island is a block of code, embedded within a CFScript tag (or a Script-based ColdFusion Component), that supports tag syntax. So, just like we can use CFScript to create a block of Script-content inside a tag-based page, we can now use the triple back-tick (```) to create a block of Tag-content inside a script-based page:

<cfscript>
	
	echo( "<p>Pre tag-island</p>" );

	```
	<p>In tag-island</p>
	```

	echo( "<p>Post tag-island</p>" );

</cfscript>

As you can see, using the triple back-tick allows us to embed plain HTML directly within our CFScript block. And, when we run the above code, we get the following page output:

Pre tag-island

In tag-island

Post tag-island

That's kind of snazzy!

Now, it's worth mentioning that ColdFusion strings have always supported a multi-line syntax. Meaning, I could write the above code as follows:

<cfscript>
	
	echo( "<p>Pre tag-island</p>" );

	echo("
		<p>
			In tag-island
		</p>
	");

	echo( "<p>Post tag-island</p>" );

</cfscript>

Notice that ColdFusion allows me to put line-breaks within my String. This gives us the same results as the Tag Island in the first example. However, there are significant differences between a multi-line string and a Tag Island:

  • A Tag Island isn't an "expression" and it doesn't return a value. It's a demarcation, like CFScript.

  • A Tag Island doesn't require escaping of single-quotes or double-quotes.

  • A Tag Island has native support for CFML Tags and, especially helpful, Child Tags.

When I see how Tag Islands work, my very first thought is using it to embed CFQuery and CFQueryParam tags within my Script-based ColdFusion components. Historically, one of the most amazing features of ColdFusion has been the ultimate simplicity with which we can author injection-proof SQL statements. Modern ColdFusion allows us to do the same with CFScript and queryExecute(); however, queryExecute() still doesn't compare with the pure elegance of the CFQuery tag.

To see what I mean, let's look at a script-based example of a SQL Query that uses a prepared statement and a single binding:

<cfscript>

	dump( getUserById( 1 ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public array function getUserById( required numeric userID ) {

		var results = queryExecute(
			sql = "
				/* DEBUG: getUserById(). */
				SELECT
					u.id,
					u.name,
					u.email
				FROM
					user u
				WHERE
					u.id = :userID
				;
			",
			params = {
				userID: {
					value: userID,
					sqlType: "integer"
				}
			},
			options = {
				datasource: "testing",
				returnType: "array"
			}
		);

		if ( ! results.len() ) {

			throw(
				type = "NotFound",
				message = "User not found",
				detail = "User with id [#userID#] could not be found."
			);

		}

		return( results );

	}

</cfscript>

Here, we're using ColdFusion's multi-line String support to define our sql property. And, this works fine. But, at least for me, it doesn't match the simplicity and elegance of the CFQuery tag; which, we can now use in CFScript with the Tag Island:

<cfscript>

	dump( getUserById( 1 ) );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public array function getUserById( required numeric userID ) {

		// NOTE: Historically, I've always authored by "Data Access Layer" (DAL) using
		// tags so that I could use the CFQuery tag. I know that I can use queryExeucte()
		// these days; but, it's still not quite as easy as writing plain CFQuery and
		// CFQueryParam tags.
		```
		<cfquery name="local.results" datasource="testing" returntype="array">
			/* DEBUG: getUserById(). */
			SELECT
				u.id,
				u.name,
				u.email
			FROM
				user u
			WHERE
				u.id = <cfqueryparam value="#userID#" sqltype="integer" />
			;
		</cfquery>
		```

		if ( ! results.len() ) {

			throw(
				type = "NotFound",
				message = "User not found",
				detail = "User with id [#userID#] could not be found."
			);

		}

		return( results );

	}

</cfscript>

This code gives us the same exact result as the all-script example. But, at least for me, this Tag Island version is easier on the eyes. This might be because I have close to two-decades of experience reading tag-based SQL, so that's where my muscle-memory is? Or, it could be the fact that the Tag Island approach uses a more concise syntax that places the query bindings closer to their point of consumption?

Your mileage may vary.

Another place that Tag Islands may be useful is in the construction of content buffers via CFSaveContent. As one example, you might use content buffers to prepare the HTML and Plain Text portions of the CFMail tag:

<cfscript>

	sendMail( "Let's talk about puppies!", "puppies" );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public void function sendMail(
		required string subject,
		required string topic
		) {

		// NOTE: Historically, I would accomplish this same thing by putting the CFMail
		// content in a .CFM file which I would then "include" into a saveContent buffer.
		// This demo makes it seem feasible to include as a tag-island; however, in
		// reality, HTML MARKUP FOR EMAILS IS VERY NASTY - as such, other than the most
		// trivial emails, there's no way I would want to mix the CFMail markup alongside
		// anything else.
		```
		<cfoutput>
			<cfsavecontent variable="local.htmlContent">
				<p>
					Dear Kim,
				</p>

				<p>
					I would like to talk to you about #encodeForHtml( topic )#. Do you
					have time later this week to discuss?
				</p>
			</cfsavecontent>

			<cfsavecontent variable="local.textContent">
				Dear Kim,

				I would like to talk to you about #encodeForHtml( topic )#. Do you have time later this week to discuss?
			</cfsavecontent>
		</cfoutput>
		```

		mail
			to = "kim@bennadel.com"
			from = "ben@bennadel.com"
			subject = subject
			type = "html"
			async = false
			{

			mailPart type = "text/html" {
				echo( htmlContent.reReplace( "(?m)^\t{4}", "", "all" ).trim() );
			}

			mailPart type = "text/plain" {
				echo( textContent.reReplace( "(?m)^\t{4}", "", "all" ).trim() );
			}

		}

	}
	
</cfscript>

As you can see, the Tag Island allows us to use the tag-based version of the CFSaveContent tag, along with a bunch of native HTML syntax.

Now, it merits mentioning that you could do the same thing with savecontent tag and the echo() function:

<cfscript>
	
	// ....

	savecontent variable="local.htmlContent" {
		echo("
			<p>
				Dear Kim,
			</p>

			<p>
				I would like to talk to you about #encodeForHtml( topic )#. Do you
				have time later this week to discuss?
			</p>
		");
	}
	savecontent variable="local.textContent" {
		echo("
			Dear Kim,

			I would like to talk to you about #encodeForHtml( topic )#. Do you have time later this week to discuss?
		");
	}

	// ....

</cfscript>

But, what if you content buffer had embedded single and double quotes? Then, suddenly you have to start escaping values. And, if your content needed some sort of embedded CFML tags, then you're probably in trouble and you have to start breaking it apart and re-constructing it in parts.

Consider a special kind content buffer, the CFXML tag - the CFXML tag is essentially a glorified CFSaveContent tag that automatically parses the content buffer into an XML document at the end. With Tag Islands, we can use embed the CFXML tag right in our script-based components:

<cfscript>

	friends = [
		{ id: 1, name: "Kim" },
		{ id: 2, name: "Arnold" },
		{ id: 3, name: "Tina" },
		{ id: 4, name: "Libby" }
	];

	saveFriends( friends );

	// ------------------------------------------------------------------------------- //
	// ------------------------------------------------------------------------------- //

	public void function saveFriends( required array friends ) {

		// Building XML documents "by hand" is a horrible experience. Using a tag-island
		// to construct an XML document could be a feasible "win". Though, to be honest,
		// I almost never create XML documents these days (JSON for the win!).
		```
		<cfoutput>
			<cfxml variable="local.body">
				<Friends>
					<cfloop index="friend" array="#friends#">
						<Friend
							id="#encodeForXmlAttribute( friend.id )#"
							name="#encodeForXmlAttribute( friend.name )#"
						/>
					</cfloop>
				</Friends>
			</cfxml>
		</cfoutput>
		```

		http
			result = "local.results"
			method = "POST"
			url = "http://127.0.0.1:52116/some-xml-api-end-point/"
			timeout = 2
			{

			httpParam
				type = "header"
				name = "Content-Type"
				value = "application/xml"
			;

			httpParam
				type = "body"
				value = toString( body )
			;

		}

		if ( ! results.statusCode.find( "2\d\d" ) ) {

			throw(
				type = "ApiFailure",
				message = "XML post failed",
				detail = "XML post returned with non-200 status code [#results.statusCode#].",
				extendedInfo = "File content: #results.fileContent#"
			);

		}

	}

</cfscript>

As you can see here, we're using the CFXML tag directly within our script-based CFML in order to construct a payload for an XML-based API (which, despite the prevalence of JSON, still exist). I am sure you could create the same XML structure with savecontent and various echo() calls and a for-in loop. But, I am almost certain it wouldn't be as easy to write, read, and maintain as just using the CFXML tag directly.

These days, I write most of my CFML code using Script. The major exceptions being anything View related, which I construct with HTML and embedded CFML tags; and, my Data Access Objects (DAOs), which I still write with tags so that I can use the CFQuery and CFQueryParam tags. So, for me personally, the huge win for Tag Islands in Lucee CFML will be the ability to write Script-based DAOs without having to give-up the simplicity, elegance, and readability of the CFQuery and CFQueryParam tags.



Reader Comments

@Chris,

Very cool -- I'm glad this struck a chord. I wish I could start using the CFQuery one today -- unfortunately, at work, I'm still on Lucee CFML 5.2.x :( . But, maybe this is a good reason for us to make the push to upgrade!

Reply to this Comment

So cool! I resigned myself to the "in-elegance" of queries within script a few years back when I went full-script so this is huge! I'm going to try this out asap.

Reply to this Comment

Ben, I can't believe you didn't test this...

Can you put a CFScript block inside of a tag island... and a tag island in that script block... and a cfscript block in that tag island... and so on, and so on?

Reply to this Comment

@Adam,

Ha ha ha, good point - it does seem like something to try. Actually, I think Gavin Pickin and Brad Wood were just talking about that on the Modernize or Die podcast:

https://youtu.be/7Zcq2kTZf_I?t=1301

Gavin said that someone tested it and you can nested cfscript and tag-islands infinitely :D

Reply to this Comment

This came up in a feed today and I found myself trawling back through the post on the Lucee dev forum - based on that, I can't believe this was ever actually implemented!
I've spent the last couple of years getting used to the queryExecute syntax and I'm comfortable using it, but I have to say that <cfquery> reads better to me also... I may well start using this again. Hurrah!

Reply to this Comment

@All,

In the month-and-a-half since I wrote this post, I've been making heavy use of both the unified tag-syntax and tag islands in Lucee CFML, and I've come to believe this kind of makes CFScript perfect:

www.bennadel.com/blog/3793-tag-islands-and-cfscript-based-tags-bring-perfection-to-coldfusion-in-lucee-cfml-5-3-4-80.htm

I'm a little bit freaking out about it. It just feels like it brings unparalleled developer ergonomics to ColdFusion that I haven't seen in any other language (though, to be fair, my expose to other languages is fairly limited). But, it's just kind of bananas. And, I'm freaking out that more people aren't freaking out about how awesome this is!

Reply to this Comment

@Ben,

Too funny! Totally agree though. Since you posted this I've used it multiple times (within query and savecontent), and agree, makes cfscript perfect! Takes the beauty and familiarity of script and mixes in a few instances where the the ease of CFML really shines.

Reply to this Comment

@Dan,

RIGHT?! It's so freaking sweet! I wanna go up to people and be like, "Have you heard the good news!" :D

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.