Taming The Header Output Of CFHTMLHead and CFAjaxProxy

Taming The Header Output Of CFHTMLHead and CFAjaxProxy

Posted by Brad Wood
Oct 21, 2009 05:14:00 UTC
Tags like CFHTMLHead, CFAjaxProxy, and CFAjaxImport don't output their content into the regular ColdFusion output buffer. Instead they put their contents into a special header buffer which is dumped into the beginning of the output right before the request is sent back to the client. But what if you want control over where their output goes? CFSaveContent doesn't work on these bad boys. And even worse, <cfcontent reset="yes"> doesn't get rid of their output. The other day I got bit when trying to return the HTML of a rendered view via a proxy in ColdBox as a JSON string. The JavaScript output of the CFAjaxProxy tag was being appended to the beginning of the response and causing the result to not be valid JSON.Looking around on the Internet turned up a function credited to Elliott Sprehn to erase any content in ColdFusion's header buffer. Looking a little further, I found this method by David Boyer to actually retrieve the header buffer. I modified the latter to work with ColdFusion 8 and 9. Without further ado:
[code]<cffunction name="clearHeaderBuffer" output="false" returntype="void">
	<cfscript>
		var local = StructNew();
		local.out = getPageContext().getOut();
		while (getMetaData(local.out).getName() Is 'coldfusion.runtime.NeoBodyContent') 
			{ local.out = local.out.getEnclosingWriter(); }
		local.method = local.out.getClass().getDeclaredMethod('initHeaderBuffer', ArrayNew(1));
		local.method.setAccessible(true);
		local.method.invoke(local.out, ArrayNew(1));
	</cfscript>
</cffunction>[/code]
[code]<cffunction name="getHeaderBuffer" output="false" returntype="any">
	<cfscript>
		var local = StructNew();
		local.out = getPageContext().getOut();
		while (getMetaData(local.out).getName() Is 'coldfusion.runtime.NeoBodyContent') 
			{ local.out = local.out.getEnclosingWriter(); }
		
		if(listFirst(server.coldfusion.productVersion) LTE 7)
			{                                      
				local.field = local.out.getClass().getDeclaredField("headerBuffer");
				local.field.setAccessible(true);
				local.buffer = local.field.get(local.out);
				return
				iif(StructKeyExists(local,'buffer'),"local.buffer.toString()",de(""));
			}
		else // CF8 intruduced new header Buffer
			{
				local.field =
				local.out.getClass().getDeclaredField("prependHeaderBuffer");
				local.field.setAccessible(true);
				local.buffer = local.field.get(local.out);
				local.output =
				iif(StructKeyExists(local,'buffer'),"local.buffer.toString()",de(""));
				
				local.field =
				local.out.getClass().getDeclaredField("appendHeaderBuffer");
				local.field.setAccessible(true);
				local.buffer = local.field.get(local.out);
				
				return local.output &
				iif(StructKeyExists(local,'buffer'),"local.buffer.toString()",de(""));
			}                              
	</cfscript>
</cffunction>[/code]
Usage looks like this:
[code]Hello World
<cfajaxproxy cfc="proxy.pxMyProxy" jsclassname="pxMyProxy" />
<cfhtmlhead text="foobar">

#getCFHtmlHead()#
#resetCFHtmlHead()#[/code]
If you were to run that code, the JavaScript generated by CFAjaxProxy is outputted below the Hello World text and that the foobar bit is outputted directly below that. Normally the header content would have been in that order, but ABOVE the Hello World text. How does it work? The getPageContext() method gives you access to to the internal guts of your ColdFusion request. getOut() hands us the JspWriter class, and a quick loop climbs to the outermost JspWriter. For the clearHeaderBuffer function we call the initHeaderBuffer method to clear out the header buffer that tags like CFHTMLHead write into. For the getHeaderBuffer function, we pull out the headerBuffer field for CF7 and for CF8 we have to get both the prependHeaderBuffer and appendHeaderBuffer fields. When Adobe introduced the Ajax tags in CF8, they split out the header buffer into two fields so they could make sure the automatically generated JavaScript from those tags would come out ahead of any other JavaScript you put in CFHTMLHead. Since we are delving into some undocumented innards of your ColdFusion request, we aren't guaranteed that this code will work in future versions without modifications. I'm also not sure if it will work on a J2EE platform other than JRun. In case you're wondering why it takes so much code to access the fields and methods of the JSPWriter object, it is because it uses something called Java Reflection. Reflection lets you access private fields and methods for the sake of debugging, or fiddling with the object's internals. Generally this is something you want to be careful about doing. Also note, the above code does NOT work on OpenBD nor Railo. That is because they use different internal names for their classes and fields. What would be ideal is if Adobe, Railo, and OpenBD would place documented methods in the pageContext object to get the header buffer. If you're interested, the Model Glue code base has a OpenBD and Railo version of clearHeaderBuffer in their abstract remoting service. I know of no OpenBD or Railo version of getHeaderBuffer. I would write one, but I don't have an OpenBD or Railo server handy. Perhaps someone can volunteer.

 


David Boyer

Great post! I've been too busy to look into CF8/9 compatibility for the functions so I'm really thankful you did the work :)

I think this is one of those things that Adobe overlooked when they created CfHtmlHead and I didn't click that those other tags would work in a similar way. Guess my personal mission is to get on the CF10 pre-release group and hound Adobe for native versions of these functions.

Thanks for the mention too.

Sebastiaan

Could this also come in handy when using CFHTMLHEAD within CFSAVECONTENT tags to prevent what's in the CFHTMLHEAD to be posted twice? F.ex. if you use CFFORMPROTECT which incorporates a CFHTMLHEAD tag to output the needed javascript files to the <head>-tag, it is posted double when I use a CFSAVECONTENT tag to build my all of my displayfiles beofre sending them to the browser. My workaround now is to comment out the CFHTMLHEAD tags in CFFormProtect and include them "by hand" in the head of the HTML-document.

Pete Freitag

Great post, I wish I had this a year ago!

Brad Wood

@Sebastiaan: That's a good question. I think you might have something fishy going on since the CFHTMLHead content should only be placed in header once provided the actual CFHTMLHead tag is only executed once. Is there any way you running the CFFormProtect code twice?

I ran tests capturing code with CFHTMLHead inside a CFSaveContent it appears to only append to the header buffer once. If you were to use the code in my post, it would certainly give you the contents of your header buffer, but I think you would find you still had your JavaScript in their twice.

Like I said, I'd check to make sure the CTHTMLHead tag isn't getting run twice.

Sebastiaan

Brad, the thing is that all the JS-files are indeed doubled up in the header. The CFFormProtect code otherwise is only executed once in the HTML-code when it's within a CFSaveContent block.

Mark Mazelin

Awesome -- this is just what I needed. Thanks!

Paul Klinkenberg

I actually wrote the same thing for Railo, which can be found on my blog: http://www.coldfusiondeveloper.nl/post.cfm/clearing-the-cfhtmlhead-buffer-in-railo

And what I just noticed today: all the code and references lying around on the internet for resetCfhtmlhead originate from the answer by Elliot Sprehn to my questions about it, 2,5 years ago :-)

Brad Wood

@Paul: Thanks for the Railo version. I really wish all three CFML platforms would add standard methods to handle the header buffer(s).

Site Updates

Entry Comments

Entries Search