Ask a Jedi: Getting a bit fancier with a layout custom tag
John asks:
Somewhere about a year ago, I found an article on using a layout.cfm page "custom tag" to layout the template for the site. I include a header, menu and footer. I wrap this around all my pages and volia we have a fairly robust template engine. Only now with all the power of the cfc and a huge whole in my plan, I need a new way to do this layout. I need to be able to create different layouts in the content area, but make them reusable through out the site.
With the simple cfcase value="start" and end option I do not have a way to specify the or dynamically call a different layout page without having to write an entire new layout page with that code in there. If that is the best way I can do that but, there has to be a way to make it and I can not wrap my brain around that process.
So normally I don't worry too much about completely different templates. Most sites I've worked on will use one main layout with perhaps just a few variations. The inner content may move from two columns to one column perhaps. To handle cases like that, I simply add new attributes to my custom tag to let me specify which to use, and I try to gauge which layout is used most often and make that the default.
But how would you handle a case where the layout you want is really varied? As always, there are a few things to consider. One simple way is to just use multiple custom tags. So you may have a productlayout.cfm and a reviewlayout.cfm file. This works ok if your file has a hard coded template. I.e., if you always know foo.cfm is a product page, you wrap it with productlayout.cfm. But again - that's hard coded and won't always be appropriate, especially if you want the layout to be chosen by a non-technical user who doesn't want to edit code.
Another possible way of doing this is demonstrated in Galleon. For that project I created a layout custom tag that takes a template attribute. The code for this tag is here:
<!--- Because "template" is a reserved attribute for cfmodule, we allow templatename as well. --->
<cfif isDefined("attributes.templatename")>
<cfset attributes.template = attributes.templatename>
</cfif>
<cfparam name="attributes.template">
<cfparam name="attributes.title" default="">
<cfset base = attributes.template>
<cfif thisTag.executionMode is "start">
<cfset myFile = base & "_header.cfm">
<cfelse>
<cfset myFile = base & "_footer.cfm">
</cfif>
<cfinclude template="../pagetemplates/#myFile#">
This layout tag takes a templatename or template attribute (I use both since template is reserved in cfmodule). This points to the base file name inside a pagetemplates folder. So if I request templatename="main", the code will either load main_header.cfm or main_footer.cfm based on the execution mode. This lets me do stuff like this:
<cf_layout template="main">
...
</cf_layout>
Or...
<cf_layout template="#session.mylayout#">
...
</cf_layout>
Again - this is just one example of how it could be done.
Comments
<cf_layoutparam attributes... />
</cf_layout>
Obviously, a VERY small, nitpicky difference of opinion. ;)
I _do_ think cfassociate is cool. Custom tags are under appreciated. :)
In my Application.cfc, my call to <cflayout> is this:
<cffunction name="onRequest">
<cfargument name="whatever">
<cf_layout><cfinclude template="#arguments.whatever#"></cf_layout>
</cffunction>
Done. Never ever have to touch that code again. On my init pages for my applications, if I need something that cf_layout needs to be aware of, then I use cf_layoutparam to pass in those attributes and I'm done.
We call the concept you are refer to here skinning. It's a little more than what the article mentioned and would be pretty hard to cover in a single blog post. I think for simplistic solutions this is about as good as you can get Ray. If your going to take it to another level then it should be a whole framework rather than more of add a piece here and there. This is an excellent article.


<cf_layout>
<cf_layoutparam attributes... />
<cf_layout>
Then you can keep the layout shell as generic as possible on a page by page basis and if there's an additional parameters that need to be passed in (such as template), then you can pass it in via cf_layoutparam, but it's not required (meaning, layout.cfm has a default mode). The <cfassociate> tag is awesome/fun to play around with. A lot of power you can add to custom tags that way.