Creating A ColdFusion CFSetting Tag With Open / Close Tags
In ColdFusion, the CFSetting tag is not designed to have an open and a close tag. It is merely used to define page processing rules for the rest of the page, NOT just for a portion of the rest of the page. I just jumped on a project where someone was trying to this:
<!--- Enable output only for this block. ---> | |
<cfsetting enablecfoutputonly="true"> | |
<p> | |
Lost of code here | |
</p> | |
</cfsetting> |
This was on an included page. I was trying to modify the parent page and was going nuts trying to figure out why my only SOME of my code was not displaying after the include. When I went into the include, it became obvious, and very irritating. Each CFSetting tag with EnableCFOutputOnly set must have a matching CFSetting tag that changes that setting back (if you want to change it back):
<!--- Enable output only. ---> | |
<cfsetting enablecfoutputonly="true" /> | |
<p> | |
Lost of code here | |
</p> | |
<!--- Disable output only. ---> | |
<cfsetting enablecfoutputonly="false" /> |
I, however, do like the idea of creating a CFSetting tag that DOES work on a block of code. So, I set about creating one myself. The first obstacle was determining if CFSetting EnableCFOutputOnly was turned on or off at the start of the tag. I starting digging through the GetPageContext() object. I figured it had to be a variable set somewhere. After about an hour of searching it dawned on me!! I don't need to find a variable for it - I just need to test it.
To test if output is required, just try to write some text and see if it actually get's written:
<!--- Test for EnableCFOutputOnly. ---> | |
<cfsavecontent variable="strOutputTest">test</cfsavecontent> | |
<!--- | |
Check to see if the tested string has any length. If it does, | |
then CFOutput is not required. If it does not, then CFOutput | |
was in fact, required. | |
---> | |
<cfif strOutput.Length()> | |
<!--- CFOutput NOT required. ---> | |
<cfelse> | |
<!--- CFOutput required. ---> | |
</cfif> |
Once I figured this out, the custom tag itself is not TOO complicated. All you have to do is check the current status of EnableCFOutputOnly, store it in the tag data, override it, then, in the close tag, restore it to what it was. The complication is that if you have nested EnableCFOutputOnly settings, then to output in a sub-block, you have to Undo the setting several times, which then of course requires resetting it several times at the end:
<!--- Kill extra output. ---> | |
<cfsilent> | |
<!--- | |
Check to see which mode of the tag we are in. If we are | |
in the start mode of the tag then we want to param the | |
attributes and check to see if cfoutput is required for | |
data output. | |
---> | |
<cfif NOT CompareNoCase( THISTAG.ExecutionMode, "Start" )> | |
<!--- | |
Param the tag attributes. These are the attributes | |
that are normally allowed for the CFSetting tag. | |
---> | |
<cfparam | |
name="ATTRIBUTES.requesttimeout" | |
type="string" | |
default="" | |
/> | |
<cfparam | |
name="ATTRIBUTES.showdebugoutput" | |
type="string" | |
default="" | |
/> | |
<cfparam | |
name="ATTRIBUTES.enablecfoutputonly" | |
type="string" | |
default="" | |
/> | |
<!-- | |
Check to see if CFOutput is required. We can | |
check this by storing a string and then testing | |
it for a length. | |
---> | |
<cfsavecontent | |
variable="strCFOutputTest" | |
>test</cfsavecontent> | |
<!--- Check to see if the test resulted in a length. ---> | |
<cfif Len( strCFOutputTest )> | |
<!--- CFOutput is not required. ---> | |
<cfset THISTAG.CFOutputRequired = false /> | |
<cfelse> | |
<!--- CFOutput IS currently required. ---> | |
<cfset THISTAG.CFOutputRequired = true /> | |
<!--- | |
When cfoutput is required, things get a bit | |
more tricky. Enabling it once might not | |
show any output. We need to keep disabling | |
it until output is available. | |
Check to see if we are going to disabling | |
CFOutput for this code block. | |
---> | |
<cfif ( | |
IsBoolean( ATTRIBUTES.enablecfoutputonly ) AND | |
(NOT ATTRIBUTES.enablecfoutputonly) | |
)> | |
<!--- | |
Since we are going to disable the cfoutput | |
requirements, we need to keep doing so | |
until output is allowed. Set a counter for | |
the number of nested tags. | |
---> | |
<cfset THISTAG.CFSettingCounter = 0 /> | |
<!--- | |
Keep looping until the test content has a | |
length. | |
---> | |
<cfloop condition="NOT Len( strCFOutputTest )"> | |
<!--- Disable output requirements. ---> | |
<cfsetting enablecfoutputonly="false" /> | |
<!--- Try getting output again. ---> | |
<cfsavecontent | |
variable="strCFOutputTest" | |
>test</cfsavecontent> | |
<!--- Add one to counter. ---> | |
<cfset THISTAG.CFSettingCounter = ( | |
THISTAG.CFSettingCounter + 1 | |
) /> | |
</cfloop> | |
</cfif> | |
</cfif> | |
<!--- Check to see which page values we need to set. ---> | |
<cfif Len( ATTRIBUTES.requesttimeout )> | |
<cfsetting | |
requesttimeout="#ATTRIBUTES.requesttimeout#" | |
/> | |
</cfif> | |
<cfif Len( ATTRIBUTES.showdebugoutput )> | |
<cfsetting | |
showdebugoutput="#ATTRIBUTES.showdebugoutput#" | |
/> | |
</cfif> | |
<cfif Len( ATTRIBUTES.enablecfoutputonly )> | |
<cfsetting | |
enablecfoutputonly="#ATTRIBUTES.enablecfoutputonly#" | |
/> | |
</cfif> | |
<!--- | |
We only want to set it back if the tag had a REAL | |
closing end tag. If it had a self closing tag, | |
then there will be no generated content. | |
YES, it maybe be possible to be using this and NOT | |
actually create any generated content. But in that | |
case, the user should be using a CFSilent tag, NOT a | |
CFSetting tag. | |
Also, we ONLY care about the end tag if we set the | |
EnableCFOutputOnly attribute. None of the other | |
attributes make sense having an end tag. | |
---> | |
<cfelseif ( | |
THISTAG.GeneratedContent.Length() AND | |
IsBoolean( ATTRIBUTES.EnableCFOutputOnly ) | |
)> | |
<!--- | |
This is the end mode of the tag. Now, we need to | |
restore the enablecfoutput mode that was set when | |
this tag was first executed. Check to see which | |
kind of change we are making, or rather, what did | |
we have beforehand. | |
---> | |
<cfif THISTAG.CFOutputRequired> | |
<!--- | |
CFOutput was required before. If we turned it | |
on this time, we have to turn it off this | |
time instead of just resetting it. If we | |
attempt to just reset it, then we will | |
merely be adding another nesting of CFSetting | |
which is not what we want to do. | |
---> | |
<cfif ATTRIBUTES.EnableCFOutputOnly> | |
<!--- | |
Counteract this one by just turning it | |
off. This will put us back to the parent | |
setting. | |
---> | |
<cfsetting | |
enablecfoutputonly="false" | |
/> | |
<cfelse> | |
<!--- | |
We were briefly turning it off. Therefore, | |
we may need to turn it back on several times | |
to mimic the original nestinf of the | |
CFSetting tags. | |
---> | |
<cfloop | |
index="intI" | |
from="1" | |
to="#THISTAG.CFSettingCounter#" | |
step="1"> | |
<cfsetting | |
enablecfoutputonly="true" | |
/> | |
</cfloop> | |
</cfif> | |
<cfelseif ATTRIBUTES.EnableCFOutputOnly> | |
<!--- | |
Output was NOT required previously but we set | |
it for the duration of this tag. Now, just | |
turn it off. | |
We dont care if we turned it off. You cannot | |
nest "false" output enabling, only "true" ones. | |
---> | |
<cfsetting | |
enablecfoutputonly="false" | |
/> | |
</cfif> | |
</cfif> | |
</cfsilent> |
Now, you can use this tag to do the following:
<cfsetting enablecfoutputonly="true" /> | |
<p> | |
Text area [ A ] | |
</p> | |
<cf_cfsetting enablecfoutputonly="true"> | |
<p> | |
Text area [ B ] | |
</p> | |
<cfoutput> | |
<p> | |
Text area [ C ] | |
</p> | |
</cfoutput> | |
<cf_cfsetting enablecfoutputonly="false"> | |
<p> | |
Sub area [ 1 ] | |
</p> | |
<cfoutput> | |
<p> | |
Sub area [ 2 ] | |
</p> | |
</cfoutput> | |
</cf_cfsetting> | |
<p> | |
Sub area [ D ] | |
</p> | |
</cf_cfsetting> | |
<p> | |
Text area [ E ] | |
</p> | |
<cfsetting enablecfoutputonly="false" /> | |
<p> | |
Text area [ F ] | |
</p> | |
<cfsetting enablecfoutputonly="false" /> | |
<p> | |
Text area [ G ] | |
</p> | |
<cfsetting enablecfoutputonly="false" /> | |
<p> | |
Text area [ H ] | |
</p> | |
<cfoutput> | |
<p> | |
Text area [ I ] | |
</p> | |
</cfoutput> |
If the above code used a standard CFSetting tag, then you would get:
Text area [ C ]
Sub area [ 2 ]
Text area [ F ]
Text area [ G ]
Text area [ H ]
Text area [ I ]
Text area [ E ] would not show since the EnableCFOutputOnly attribute would NOT be closed by the close CFSetting tag. And, the Sub area [ 1 ] would not show since the enablecfoutputonly="false" of the second nested tag would NOT override both previous CFsetting attributes. However, since this custom tag restores the pre-tag CFOutput requirements, you end up getting:
Text area [ C ]
Sub area [ 1 ]
Sub area [ 2 ]
Text area [ F ]
Text area [ G ]
Text area [ H ]
Text area [ I ]
Notice that Sub area [ 1 ] is getting displayed. That is because the enablecfoutputonly="false" overrides BOTH the previous CFSetting attributes. And yet, at the same time, Text area [ D ] does NOT show since after the close cf_cfsetting tag, the enablecfoutputonly="true" of the previous tag is restored. Notice that if you took out the nested tags, you would only get areas F, G, H, and I, which is according to the rules of CFSetting.
So basically, this cf_CFSetting tag allows you to determine the requirements of CFOutput regardless of the nested nature of the CFSetting tags before and after the current tag.
Want to use code from this post? Check out the license.
Reader Comments
There's some slick logic behind this tag. Great work, man.
I'd like to share something I just found out today. CFsetting with enablecfoutputonly set to "true" is meaningless for anything within a CFform.
Try this out.
<cfsetting enablecfoutputonly="true">
No cfoutput<br />
<cfoutput>
Yes cfoutput<br />
</cfoutput>
<cfform action="#CGI.script_name#">
CFFORM No cfoutput<br />
<cfoutput>
CFFORM Yes cfoutput<br
</cfoutput>
</cfform>
I figured this out when I had a custom tag inside a CFform, and I was getting all this white space that was supposed to be suppressed. The custom tag was using CFsetting to silent out everything except for whatever was inside CFoutput tags. It was only through trial and error that I was able to figure this bug out.
I ended up going back to CFprocessingdirective with suppresswhitespace set to "true" to solve the problem. Nonetheless, I do consider this a fail for CFform.
What's really bizarre is that whatever is inside a CFform does NOT get treated as if it were within CFoutput tags. For example, this code does not actually output the contents of the variable.
<cfform action="#CGI.script_name#>
#CGI.script_name#
</cfform>
This is really strange behavior. I imagine something like this is so small that no one will really care. Plus, CFform just has such a bad rap as it is.
Awesome! This just answered a bunch of questions and saved me some time. Thanks!