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, " class=""#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>

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

Top comments (0)

Retry later
👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay