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!