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>

Neon image

Build better on Postgres with AI-Assisted Development Practices

Compare top AI coding tools like Cursor and Windsurf with Neon's database integration. Generate synthetic data and manage databases with natural language.

Read more →

Top comments (0)

👋 Kindness is contagious

Engage with a wealth of insights in this thoughtful article, valued within the supportive DEV Community. Coders of every background are welcome to join in and add to our collective wisdom.

A sincere "thank you" often brightens someone’s day. Share your gratitude in the comments below!

On DEV, the act of sharing knowledge eases our journey and fortifies our community ties. Found value in this? A quick thank you to the author can make a significant impact.

Okay