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

Using ColdFusion Custom Tags To Create An HTML Email DSL In Lucee CFML 5.3.7.47, Part XII

By Ben Nadel on
Tags: ColdFusion

Last week was InVision's "Sync Week" - our annual all-company meetup and hackathon. And, for my hackathon topic, I decided to try and apply my ColdFusion custom tag DSL (Domain Specific Language) for HTML emails to our application's transactional emails. It's still in the proof-of-concept (POC) phase; but, I learned a lot from 3-days of heads-down HTML email hackery; and, I've tried to pull those learnings back into my ColdFusion custom tag concept. One of the first issues that I ran into was that I needed to just normalize all Margins on block-level entities.

View this code in my ColdFusion Custom Tag Emails project on GitHub.

In my original ColdFusion custom tag implementation, title entities like h1 and h2 had different margins than copy entities like p and ol. Once I started to apply this to real-world emails, however, it quickly became overly complicated. Plus, I realized that I needed a way for "user land" to define overrides for these margins on more of a global level.

So, I went back and updated the core entities in three ways:

  • All block-level entities have a default margins value of none normal.

  • I reduced the number of margin "keywords" to be:

    • none
    • quarter4px
    • half8px
    • normal16px
    • double32px
  • Arbitrary numeric values can now be used, and will be assumed to px values.

Margin keywords and pixel values can be mixed-and-matched. So, it's completely reasonable to use margins like:

  • margins="none normal"
  • margins="normal double"
  • margins="none 5"
  • margins="20 normal"
  • margins="30 50"

Once I had the block-level margins normalized, I then added a way for them to be customized on a per-email basis using the core:Provide tag. Now, you can define the default margins attribute for each block-level entity. Here are the possible provider keys:

  • "margins.blockquote"
  • "margins.h1"
  • "margins.h2"
  • "margins.h3"
  • "margins.h4"
  • "margins.h5"
  • "margins.hr"
  • "margins.ol"
  • "margins.p"
  • "margins.pre"
  • "margins.table"
  • "margins.ul"

Each block-level entity then looks for these provider values during execution. Here's a snippet from the h1.cfm ColdFusion custom tag:

<!--- Get default margins for entity. --->
<cfset entityMargins = getBaseTagData( "cf_email" ).providers[ "margins.h1" ] />

<!--- Define custom tag attributes. --->
<cfparam name="attributes.class" type="string" default="" />
<cfparam name="attributes.margins" type="string" default="#entityMargins#" />
<cfparam name="attributes.style" type="string" default="" />

To this in action, I've added another example to the GitHub repository:

<!--- Import custom tag libraries. --->
<cfimport prefix="core" taglib="./core/" />
<cfimport prefix="html" taglib="./core/html/" />

<!--- // ------------------------------------------------------------------------- // --->
<!--- // ------------------------------------------------------------------------- // --->

<core:Email
	subject="Normalizing block-level margins"
	teaser="Keeping space manageable!">

	<!---
		Out of the box, all of the block-level HTML entities have a default "margins"
		value of "none normal"; however, we can customize each entity at a high-level by
		defining a Provide-based override:
	--->
	<core:Provide name="margins.h1" value="none double" />
	<core:Provide name="margins.p" value="none 20" />
	<core:Provide name="margins.ul" value="half 20" />

	<core:Body>

		<html:h1>
			Normalizing block-level margins
		</html:h1>

		<html:p>
			By default, all block-level entities, such as headers and paragraphs, have a
			<html:code>margins</html:code> value of <html:code>none normal</html:code>.
			But, these margins can be overridden using the
			<html:code>&lt;core:Provide&gt;</html:code> tag. Each block-level entity has
			it's own customizable provider:
		</html:p>

		<html:ul>
			<html:li>"margins.blockquote"</html:li>
			<html:li>"margins.h1"</html:li>
			<html:li>"margins.h2"</html:li>
			<html:li>"margins.h3"</html:li>
			<html:li>"margins.h4"</html:li>
			<html:li>"margins.h5"</html:li>
			<html:li>"margins.hr"</html:li>
			<html:li>"margins.ol"</html:li>
			<html:li>"margins.p"</html:li>
			<html:li>"margins.pre"</html:li>
			<html:li>"margins.table"</html:li>
			<html:li>"margins.ul"</html:li>
		</html:ul>

		<html:p>
			Margin tokens can be either "keywords" or pixel values. Supported keywords
			are:
		</html:p>

		<html:ul>
			<html:li>"quarter" <html:symbol>&rarr;</html:symbol> "4px"</html:li>
			<html:li>"half" <html:symbol>&rarr;</html:symbol> "8px"</html:li>
			<html:li>"normal" <html:symbol>&rarr;</html:symbol> "16px"</html:li>
			<html:li>"double" <html:symbol>&rarr;</html:symbol> "32px"</html:li>
		</html:ul>

		<html:p>
			Any, any numeric token will be considered a "px" value. The two types of
			tokens can be mixed-and-matched:
		</html:p>

		<html:ul>
			<html:li>
				<html:code>margins="none normal"</html:code>
			</html:li>
			<html:li>
				<html:code>margins="none double"</html:code>
			</html:li>
			<html:li>
				<html:code>margins="none 20"</html:code>
			</html:li>
			<html:li>
				<html:code>margins="5 normal"</html:code>
			</html:li>
			<html:li>
				<html:code>margins="10 20"</html:code>
			</html:li>
		</html:ul>

		<html:p margins="50 none">
			And, of course, each block-level entity can still have a per-instance margin
			customization right in the markup.
		</html:p>

	</core:Body>
</core:Email>

As you can see, I'm providing override margins for the h1, p, and ul HTML entities. Of course these default overrides can, themselves, be overridden on a per-entity basis using the margins attribute.

And now, when we run this ColdFusion code, we get the following browser output - note that I've added a dotted red border around each of the underlying margin tables so that you can see where the block-level margins are being applied:

An HTML email with normalized and customized margins in Lucee CFML.

Hopefully this strikes a nice balance between ease-of-consumption (via defaults) and low-level customization (via the margins attribute). And, of course, since this is all just a proof-of-concept so far, this is all bound to change at some point.



Reader Comments

I closed my twitter account but recently got Lucee's newsletter and guess who they were thanking to? The one and only Ben Nadel!
Man, you are like Rome, somehow there is always a path that leads to you and your blog posts.

By the way: what is "HTLM Emails", from the first paragraph? :)

Reply to this Comment

@Dani,

Ha ha, welcome back then, good sir. Oh, and good catch on the typo -- looks like I made that mistake on a bunch of different posts :( So much for copy-paste!

Reply to this Comment

Thank you Ben. About the typo, something was bothering my eyes as I was reading the post, something didn't feel right. So it wasn't me but my eyes :)

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.