Here is an interesting question:
What are the methods of dumping and can you explain them?
The simple, short answer to this is <cfdump>. The cfdump tag will take any possible value and attempt to display it's value in a nice format. However, you may be curious how cfdump works. Let's take a look at how values can be inspected and then displayed. We will do this by creating a new custom tag called simpledump. I'm using the name "simpledump" because this code will not be as good as the built-in dump tag. Why bother? Well, I'm a big fan of writing code for the fun of it. That's why I built my own blog and my own forums. You can learn a lot from just building something yourself. That being said, let's get started!
Custom tags, as a topic, are something I'll assume you have a basic knowledge of already. If not, don't worry. The only line that may be a bit odd to you will be the very first line of our code:
This line simply requires that we pass a variable named data to the tag. It is a simple way of doing validation for our tag. Everything else we write will be specific to the topic at hand - inspecting and dumping the data.
The cfdump tag first checks to see what kind of value something is. To do this, it runs various tests on the data. One of the simplest tests is for strings and numbers. Strings and numbers (and dates and boolean values like true or false) are all considered simple values in ColdFusion. There is a built-in function that tests for such values: isSimpleValue. So the first thing our tag will do is check for simple values:
<cfif len(trim(attributes.data))>
<cfoutput>#attributes.data#</cfoutput>
<cfelse>
<cfoutput>SimpleDump: Empty String</cfoutput>
</cfif>
</cfif>
What's the deal with the len(trim()) check? I'm stealing a trick from cfdump. If you pass an empty string to cfdump, the tag will output a message saying the string was empty. If it didn't do this, you may think the tag wasn't running at all. So far so good. Now let's tackle our next data type: Arrays. Once again, ColdFusion comes with a nice built-in function: isArray. The following code portion will be added to our custom tag, right before the closing cfif statement above:
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="2">Array of #arrayLen(attributes.data)# item(s)</td>
</tr>
</cfoutput>
<cfloop index="x" from="1" to="#arrayLen(attributes.data)#">
<cfoutput>
<tr>
<td>#x#</td>
<td>
</cfoutput>
<cf_simpledump data="#attributes.data[x]#">
<cfoutput>
</td>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
So, nothing terribly exciting here. I'm using a table to display my array. Like the built-in tag, cfdump, I create a header that tells me I'm working with an array. I also say how many items the array has. Sure this will be obvious, but I like the header to be more informative. I use the arrayLen function to determine how many items the array has, and for each item, I call simpledump again. This is called recursion, but don't worry about that now. Basically all I'm saying is - use the custom tag to handle the display of the array items, since I don't know what kind of data is in the array.
With me so far? Now let's add support for ColdFusion structures. Once again we have a simple function we can use: isStruct. Like the code sample above, this code block would be in the main cfif area. (Don't worry, at the end, I'll post the full code.) One little note - you will notice the isObject check. Turns out - a ColdFusion Component actually returns true for isStruct. Don't ask me why. I had forgotten about that feature when I was writing this entry and it certainly surprised me. I added the not isObject check and it seems to handle it fine.
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="2">Struct of #structCount(attributes.data)# item(s)</td>
</tr>
</cfoutput>
<cfloop item="key" collection="#attributes.data#">
<cfoutput>
<tr>
<td>#key#</td>
<td>
</cfoutput>
<cf_simpledump data="#attributes.data[key]#">
<cfoutput>
</td>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
In general, this is almost the exact same code that we used for the array dump. Once again I use a table. This time however I use the structCount function which returns the number of keys in the structure. My loop this time uses the item/collection syntax, which will basically loop once per key in the collection. The display code inside, again, is almost the exact same as the array code earlier. So now we are handling simple values, arrays, and structures. Let's look at the last type we will handle, queries. Once again, I'll show the code block then explain what I'm doing:
<cfset columns = listLen(attributes.data.columnList) + 1>
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="#columns#">Query of #attributes.data.recordCount# row(s)</td>
</tr>
<tr>
<td> </td>
<cfloop index="col" list="#attributes.data.columnList#">
<td>#col#</td>
</cfloop>
</tr>
</cfoutput>
<cfloop query="attributes.data">
<cfoutput>
<tr>
<td>#currentRow#</td>
</cfoutput>
<cfloop index="col" list="#attributes.data.columnList#">
<cfoutput><td></cfoutput>
<cf_simpledump data="#attributes.data[col][currentRow]#">
<cfoutput></td></cfoutput>
</cfloop>
<cfoutput>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
You probably guessed the function we would start off with, isQuery. Inside this block, the first thing we do is create a variable called columns. I need to know the number of columns the query has. Luckily there is a built-in value that exists in all queries, columnList. I take the length of this list and add one to it. My query display will need one column per query column (obviously), and one additional column for the row number. I use the columns value in my header so the span is correct. Note that in the header I also use the built-in value, recordCount, to report how many rows are in the query. I want the header to contain the column names, so we use the built-in variable, columnList, and loop over it. Next we begin our loop for the main data of the query. We use another built-in feature whereby the variable, currentRow, inside the output of a query, will always equal the current row of the query data. Like in the header area, we again loop over each column in the query. To display the current column and row, we use this format: attributes.data[col][currentRow]. As with our struct and array dumps, we call the custom tag to display the value of each query cell.
Last but not least, even though we said we wouldn't handle as many things as the built-in dump tag, we should at least do something for components, COM objects, and other things we can't handle. We will add one more cfelse statement to handle them. Here is the custom tag in all it's (ugly) glory:
<cfif isSimpleValue(attributes.data)>
<cfif len(trim(attributes.data))>
<cfoutput>#attributes.data#</cfoutput>
<cfelse>
<cfoutput>SimpleDump: Empty String</cfoutput>
</cfif>
<cfelseif isArray(attributes.data)>
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="2">Array of #arrayLen(attributes.data)# item(s)</td>
</tr>
</cfoutput>
<cfloop index="x" from="1" to="#arrayLen(attributes.data)#">
<cfoutput>
<tr>
<td>#x#</td>
<td>
</cfoutput>
<cf_simpledump data="#attributes.data[x]#">
<cfoutput>
</td>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
<cfelseif isStruct(attributes.data) and not isObject(attributes.data)>
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="2">Struct of #structCount(attributes.data)# item(s)</td>
</tr>
</cfoutput>
<cfloop item="key" collection="#attributes.data#">
<cfoutput>
<tr>
<td>#key#</td>
<td>
</cfoutput>
<cf_simpledump data="#attributes.data[key]#">
<cfoutput>
</td>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
<cfelseif isQuery(attributes.data)>
<cfset columns = listLen(attributes.data.columnList) + 1>
<cfoutput>
<table border="1">
<tr bgcolor="yellow">
<td colspan="#columns#">Query of #attributes.data.recordCount# row(s)</td>
</tr>
<tr>
<td> </td>
<cfloop index="col" list="#attributes.data.columnList#">
<td>#col#</td>
</cfloop>
</tr>
</cfoutput>
<cfloop query="attributes.data">
<cfoutput>
<tr>
<td>#currentRow#</td>
</cfoutput>
<cfloop index="col" list="#attributes.data.columnList#">
<cfoutput><td></cfoutput>
<cf_simpledump data="#attributes.data[col][currentRow]#">
<cfoutput></td></cfoutput>
</cfloop>
<cfoutput>
</tr>
</cfoutput>
</cfloop>
<cfoutput>
</table>
</cfoutput>
<cfelse>
<cfoutput>SimpleDump: Complex Object</cfoutput>
</cfif>
That's it! I hope you enjoyed this little trip into the various functions to introspect and play with ColdFusion data structures.