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.
Archived Comments
I prefer the:
<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.
<cf_layout>
<cf_layoutparam attributes... />
</cf_layout>
Hmm. I don't like that approach. Just because I want my layout code to have as little 'footprint' in the file as possible. By using cf_layout x=y, I only have 2 lines for layout in the template. Your method would increase that to 3.
Obviously, a VERY small, nitpicky difference of opinion. ;)
I _do_ think cfassociate is cool. Custom tags are under appreciated. :)
Ray, you're missing something. By default, there's only _EVER_ 2 lines. Layoutparam is optional. It's either there or it isn't. If it isn't used, then awesome. 2 lines. If it is used, then great... I can pass additional stuff to the custom tag and it's further expandable without ever having to touch the call to <cflayout>.
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.
I might also add that I have a disableShell="true" attributes that I pass in via cf_layoutparam and the cf_layout custom tag is responsible for knowing what to do.
No, I did get that. My thinking was that for the cases where you do need to use the optional arg, I preferred it "in tag" as opposed to a child tag. As I said - it was _just_ my personal pref.
I like using tags for presentation also. HTML isn't an object and though it can be generated with CFCs it seems more natural to generate HTML with Tags than CFCs. An integration of Controller and Modeling built in CFCs with presentation generated by tags is how we built COOP. When we do a return request via AJAX or interact via Flex/AIR applications we don't use the custom tags at all.
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.
Yeah, very nice and clean. Good idea.