Ask a Jedi: Does ColdFusion have a htmlfoot tag?

Rob asks:

Is there a <cfhtmlfoot> tag? One that would write at the end of html file, before </body> tag...

In case folks don't get why he is asking, ColdFusion comes with a cfhtmlhead tag that lets you dynamically add stuff to the HEAD portion of an HTML document. There is not a corresponding tag like what Rob wants, but there is no reason we can't hack one up in a custom tag. My solution will make use of both the Request scope and the oft-maligned (by me) onRequest function. First, a sample page:

<cf_htmlfoot text="<p>© Raymond Camden #year(now())#">
<html>

<head>
<title>Test</title>
</head>

<body>

<p>
Woohoo,web design kicks butt.
</p>

</body>
</html>

This is a trivial page with simple text on it. Note the call to the custom tag, htmlfoot, on top. The custom tag just does this:

<!--- the text to add --->
<cfparam name="attributes.text" default="">

<!--- where we store it --->
<cfparam name="request.footertext" default="">

<!--- add it --->
<cfset request.footertext &= attributes.text>

As you can see, we simply take your text, and append it to the text we want to add to the foot. This actually makes my tag better as I don't think you can have multiple cfhtmlhead tags. If I weren't so lazy, I'd also make the custom tag support this syntax:

<cf_htmlfoot>
Foo Foo
</cf_htmlfoot>

Anyway, the last step is to enable onRequest to notice the Request scope variable we created:

<cffunction name="onRequest" returnType="void">
   <cfargument name="thePage" type="string" required="true">
   <cfset var content = "">

   <cfsavecontent variable="content">
   <cfinclude template="#arguments.thePage#">
   </cfsavecontent>

   <cfif structKeyExists(request, "footertext")>
      <cfset content = replacenocase(content, "</body>", "#request.footertext#</body>")>
   </cfif>

   <cfoutput>#content#</cfoutput>
</cffunction>

There isn't much to talk about here. All I did was look for the Request variable, and if it existed, I insert it into the result HTML before outputting it to the browser. Again, I'm not a big fan of onRequest, but this is an interesting example of how one could use it.

Comments

If you are going to go with the trouble of making a custom tag, and making it really complex to handle more than just text, why not just use a cfinclude on each page before the body? that way it could embed a much richer and simpler code separeted in .cfm files.

Unless code reuse is DEEPLY related here, I dont see why doing this would be good. Maybe because cfhtmlhead doesnt make that much sense either for me :(
# Posted By Raul Riera | 4/30/08 8:56 AM
How dare you say my waste of time is a waste of time. I am highly offended!
# Posted By Raymond Camden | 4/30/08 9:01 AM
cfhtmlhead isn't a waste of time and I can certainly see some usage for cfhtmlfoot. I recently came across the benefits of cfhtmlhead and it's not really useful until you think "man, I wish I could shove this javascript/metadata up into the header of my templates without actually having to write it there."

Then the little lightbulb comes on.
# Posted By Todd Rafferty | 4/30/08 10:03 AM
cfhtmlhead is very useful.

sure you don't HAVE to have your javascript in the head tag but I like it there, its neater.

And if you use a MVC framework you don't typically have access to the htmlhead so if you do want to jam some javascript, css or metadata tags into the head of the page from your display page you need to use it.

Then again if you are using an MVC framework you could declare two variables htmlhead and htmlfoot and output them in the appropriate places in your layoutpage, then you simply need to set those variables with whatever it is you want to output when you want stuff jammed in the header or footer....no need to create a custom tag to do it, no need to use onRequestEnd.
# Posted By Gary Gilbert | 4/30/08 10:18 AM
What happened to OnRequestEnd.cfm?
# Posted By Leon Ridge-Cooke | 5/1/08 3:50 AM
Technically that isn't a suitable answer. Sure you can use it to append stuff at the end, but in this question, the idea is - I've got a full layout already but I want to insert something before the body tag.
# Posted By Raymond Camden | 5/1/08 5:59 AM
okay, what about..

at the end of the specific page
<cfset somevariable = "html goes here">

and in the OnRequestEnd.cfm
<cfif IsDefined("somevariable")>#somevariable#</cfif>
and then the rest of the "footer" layout follows.

if the html is complex, maybe use cfsavecontent?

My code is brief, probably needs some better coding.

Tell me what you think?
# Posted By Leon Ridge-Cooke | 5/1/08 6:07 AM
While that does work, it doesn't technically apply to the problem at hand in this blog post - which is to dynamically insert something before the body tag. I'm splitting hairs here probably. ;)
# Posted By Raymond Camden | 5/1/08 6:10 AM
no problem. I have learnt a lot from you. Just wanted your opinion on my method.

Thanks Ray.
# Posted By Leon Ridge-Cooke | 5/1/08 6:12 AM
Well, if we agree that what you want isn't the best specifically for what we discuss here, let me comment in general.

I don't think onRequestEnd is a good way to do layout stuff. Nor would I recommend Application.cfm. Nor onRequestStart/End.

I recommend using custom tags for layout. So my page would look like so:

<cf_layout title="foo">
some page
</cf_layout>

I've got a blog entry on it here on the blog somwhere.

The reason I don't like using app.cfm/onrequestend.cfm/app.cfc is that if you have a page that DOESN'T need layout, like a popup window, then you have to embed code in those files to block it. To me, this should be done on the page instead.
# Posted By Raymond Camden | 5/1/08 6:19 AM
ok, i'm an old school cfer. I learnt from CF5. I have all my page headers, menus and footer drawn from application.cfm and onrequestend.cfm. These pages have cfincludes to draw in headers, menus and footers. When i need a page, like a popup, i call the page with a url variable something like popup.cfm?noheaders.

The application.cfm looks for this and does not include "header.cfm", "menu.cfm", etc. The same for OnRequestEnd.

I've always had a gut feeling that this is wrong. I will look into it when I have some time. Unfortunately my current client wants results now, so I have to code as I know how to code.
# Posted By Leon Ridge-Cooke | 5/1/08 6:29 AM