DEV Community

Cover image for structToTable - Generate HTML/Text Output for Display/Email
James Moberg
James Moberg

Posted on

2

structToTable - Generate HTML/Text Output for Display/Email

When working on new logic and I want to dump a variable, I often use an internal CF_DumpLite CFC/CFTag that we developed that minimizes HTML output, honors original form & query column order & case, redacts CC values and is nonce aware. If I need to view the data types, I switch it up to use the cf_dump CFTag by Alexander Kwaschny.

We occasionally need to output data from an unordered struct in an email or to the webpage and it's not optimal to use either of these developer-centric dump tools for the general public. Even though we've been manually displaying tables for years, we've tended to repeat ourselves. I wrote this UDF in an effort to avoid having to do that.

structToTable is a ColdFusion UDF that will accept a struct with key-value pairs to generate legacy-valid, email-friendly html table and text-only key-value output.

We're now using this UDF when generating email messages so that we have both HTML and TEXT versions of the data. Enjoy!

Example Usage

result = structToTable(data=CGI,
  keysToRemove="SERVER_PORT", sortOrder="https,auth_user");

writeoutput(result.html);

writeoutput("\<pre\>" & result.text & "\</pre\>");
Enter fullscreen mode Exit fullscreen mode

Source Code

https://gist.github.com/JamoCA/bc5c58e829e191947f2e34289fd98a5a

<cfscript>
/**
* structToTable UDF
* @displayname structToTable
* @author James Moberg http://sunstarmedia.com, @sunstarmedia
* @version 1
* @lastUpdate 10/28/2024 15:58
* @gist https://gist.github.com/JamoCA/bc5c58e829e191947f2e34289fd98a5a
* @blog https://dev.to/gamesover/structtotable-generate-htmltext-output-for-displayemail-1mk4
* @twitter https://x.com/gamesover/status/1851046259387469955
* @LinkedIn https://www.linkedin.com/posts/jamesmoberg_coldfusion-activity-7256824771630243841-gYX7
* @param String data A struct with key - simple value pairs. Required.
* @param String tableId Table ID to use. Optional.
* @param String tableClass Table class name(s) to use. Optional.
* @param Any keysToRedact Array to string list of keys to redact. Optional.
* @param Any keysToRemove Array to string list of keys to remove. Optional.
* @param Any sortOrder Array to string list of keys to prioritize in order (if they exist). Optional.
* @param Boolean niceVars An option to humanize variable names. Optional. Default = true
*/
public struct function structToTable(
required struct data,
string tableId="",
string tableClass="",
any keysToRedact="",
any keysToRemove="",
any sortOrder="",
boolean niceVars=true
) output=false hint="I accept a struct with key-value pairs to generate legacy-valid, email-friendly html table and text-only key-value output" {
local.data = duplicate(arguments.data);
local.html = ["<table"];
local.text = [];
local.td = "<td style=""word-break:break-word;"">";
local.row = 0;
local.sortedKeys = [];
string function humanizeHeader(string string) hint="Convert a camelized/dasherized/underscored string into a humanized one" {
arguments.string = rereplace(arguments.string, "([^[:alnum:]_-]+)", " ", "ALL").replaceAll("_", " ");
arguments.string = rereplace(arguments.string, "([A-Z]+)([A-Z][a-z])", "\1 \2", "ALL");
return rereplace(arguments.string, "([a-z\d])([A-Z])", "\1 \2", "ALL").replaceAll("\s+", " ");
}
if (len(trim(arguments.tableId))) arrayappend(local.html, " id=""#trim(arguments.tableId)#""");
if (len(trim(arguments.tableClass))) arrayappend(local.html, " tableClass=""#trim(arguments.tableClass)#""");
// update Sort Order
local.sortOrder = (isarray(arguments.sortOrder)) ? arguments.sortOrder : (issimplevalue(arguments.sortOrder)) ? listtoarray(arguments.sortOrder) : [];
// Get initial keys with original key case, append to sort order if they don't exist in user-defined order
local.initialKeys = listtoarray(structkeylist(local.data));
for(local.key in local.sortOrder){
if (arrayfindnocase(local.initialKeys, local.key)){
arrayappend(local.sortedKeys, local.initialKeys[arrayfindnocase(local.initialKeys, local.key)]);
}
}
for(local.key in local.data){
if (!arrayfindnocase(local.sortedKeys, local.key)){
arrayappend(local.sortedKeys, local.key);
}
}
// remove keys
local.keys = (isarray(arguments.keysToRemove)) ? arguments.keysToRemove : (issimplevalue(arguments.keysToRemove)) ? listtoarray(arguments.keysToRemove) : [];
for(local.key in local.keys) {
if (arrayfindnocase(local.sortedKeys, local.key)){
arraydeleteat(local.sortedKeys, arrayfindnocase(local.sortedKeys, local.key));
}
}
arrayappend(local.html, " border=""1"" cellspacing=""0"" cellpadding=""2"">");
for(local.key in local.sortedKeys){
local.row += 1;
local.rowAttributes = !(local.row mod 2) ? " style=""background-color:##e0e0e0;"" class=""odd""" : "";
local.keyText = (arguments.niceVars) ? humanizeHeader(local.key) : local.key;
arrayappend(local.html, "<tr#local.rowAttributes#><td style=""font-weight:bold; vertical-align:top;"">#local.keyText#:</td>");
local.val = local.data[local.key];
if (listfindnocase(arguments.keysToRedact, local.key)){
arrayappend(local.html, "#local.td#REDACTED</td>");
arrayappend(local.text, "#local.keyText#: REDACTED");
} else if (isnull(local.data[local.key])){
arrayappend(local.html, "#local.td#NULL</td>");
arrayappend(local.text, "#local.keyText#: NULL");
} else if (!issimplevalue(local.data[local.key])){
arrayappend(local.html, "#local.td#[Complex Object]</td>");
arrayappend(local.text, "#local.keyText#: [Complex Object]");
} else if (isvalid("url", local.data[local.key])){
arrayappend(local.html, "#local.td#<a href=""#local.data[local.key]#"" target=""_blank"" rel=""nofollow noreferrer"">#encodeforhtml(local.data[local.key])#</a></td>");
arrayappend(local.text, "#local.keyText#: #local.val#");
} else {
arrayappend(local.html, local.td & encodeforhtml(local.val) & "</td>");
arrayappend(local.text, "#local.keyText#: #local.val#");
}
arrayappend(local.html, "</tr>");
}
arrayappend(local.html, "</table>");
return [
"html": arraytolist(local.html, " ")
,"text": arraytolist(local.text, chr(13) & chr(10))
];
}
</cfscript>

Postmark Image

Speedy emails, satisfied customers

Are delayed transactional emails costing you user satisfaction? Postmark delivers your emails almost instantly, keeping your customers happy and connected.

Sign up

Top comments (0)

Heroku

This site is powered by Heroku

Heroku was created by developers, for developers. Get started today and find out why Heroku has been the platform of choice for brands like DEV for over a decade.

Sign Up

👋 Kindness is contagious

Dive into an ocean of knowledge with this thought-provoking post, revered deeply within the supportive DEV Community. Developers of all levels are welcome to join and enhance our collective intelligence.

Saying a simple "thank you" can brighten someone's day. Share your gratitude in the comments below!

On DEV, sharing ideas eases our path and fortifies our community connections. Found this helpful? Sending a quick thanks to the author can be profoundly valued.

Okay