Using ColdFusion Custom Tags To Create An HTML Email DSL In Lucee CFML 5.3.7.47, Part XII
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 ofnone normal
.I reduced the number of margin "keywords" to be:
none
quarter
→4px
half
→8px
normal
→16px
double
→32px
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><core:Provide></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>→</html:symbol> "4px"</html:li>
<html:li>"half" <html:symbol>→</html:symbol> "8px"</html:li>
<html:li>"normal" <html:symbol>→</html:symbol> "16px"</html:li>
<html:li>"double" <html:symbol>→</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:
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.
Want to use code from this post? Check out the license.
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? :)
@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!
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 :)
@Dani,
Consider the typos fixed 💪 - thanks again!
🙏 keep mentoring us master