Sunday, April 17, 2011

Does ColdFusion have an answer to ASP.NET's Master Pages?

I'm working on a website that was coded in ColdFusion. I have a CSS/HTML template I would like to apply to the content of every page, without duplicating any more code than necessary. I've gotten kind of spoiled by ASP.NET's master pages, which would be my preferred way to implement this site. Unfortunately, that option is unavailable to me. This site has to run on Coldfusion MX 7. Also, the developer leading the project doesn't like Fusebox, so that option's out.

The main navigation, graphical header, and footer will be the same on every page. The title tag, meta tags, and level-2 navigation will likely vary from page to page. Aside from that, only the page's "main content area" will be different.

Given these parameters, how can I code the site for maximum maintainability?

From stackoverflow
  • check out CFINCLUDE

    Joshua Carmody : I've used cfinclude to include in headers, footers, other elements, etc, but I was hoping for something a little more powerful.
  • You may try one of the MVC frameworks with template support (almost everyone has it).

    ColdBox, Model-Glue, Mach-II, Fusebox...

    This Galleon Forum Ports Comparisons page highlights how each framework handles Templates...

  • There are a huge number of ways to do this with ColdFusion.


    Application.cfc is executed on every request and has two methods (onRequestStart and onRequestEnd) that can be used to prepend/append content to the main script in a page.

    Also worth noting, it is possible to extend/inherit your Application.cfc, allowing for a more complex set of RequestStart/End events. More details here and here.


    Custom Tags allow you to create a tag that you can wrap around each template to apply the layout/etc. It also allows attributes/etc to define common but changing text.

    For example:

    <cf_page PageTitle="My Page">
        [main page content]
    </cf_page>
    

    And inside the custom tag (page.cfm) you have:

    <cfif ThisTag.ExecutionMode EQ 'start'>
        <cfparam name="Attributes.PageTitle" default=""/>
        <cfcontent reset/><cfoutput><!DOCTYPE html>
        <html>
        <head>
         <title>My Website - #Attributes.PageTitle</title>
         [styles and scripts and stuff]
        </head>
        <body>
         <div id="heading">
          <img src="my_website_logo.png" alt="My Website"/>
         </div>
         <ul id="mainmenu" class="nav">
          [menu]
         </ul>
         <h1>#Attribute.PageTitle#</h1>
        </cfoutput>
    <cfelse>
        <cfoutput>
         <div id="footer">
             [footer]
         </div>
        </body></html></cfoutput>
    </cfif>
    

    And of course you can either create multiple custom tags, or one tag which works in multiple ways depending on the Attributes specified.


    Henry has already mentioned MVC Frameworks, but you don't need to do MVC to make use of templating/layout functionality.

    Fusebox can do MVC, but it doesn't require you to do so, and eitherway FB's ContentVariables are a good tool for implementing modular content with - unless your lead developer can justify his dislike for Fusebox (and suggest an alternative that fits your project better!) then there is absolutely no reason not to go for it - it is a mature and well-known framework, easy to use, plenty of developers, and so on.

    However, if Fusebox really is not an option, take a look at Charlie Arehart's list of frameworks - that page in general is a huge list of tools worth looking at.


    Anyway, that should give you enough things to consider for now...

    Joshua Carmody : Custom tags are a great idea. Thank you.
    Nathan Strutz : Peter, freakin' great post. One thing you could add is with Application.cfc, you can inherit parent onRequestStart/End for that nice master/child/grandchild page effect, calling super methods. Oh, and Application.cfm/OnRequestEnd.cfm and cfinclude the parent file in lieu of that.
    Henry : Never thought of using custom tag for doing this... :) cool, good for making it work quickly.
    Peter Boughton : Nathan: Yeah, Application.cfc inheritance is a little fiddly though? I'll add a note and link for completeness though.
    Peter Boughton : Henry: Lots of people forget about custom tags, but they can be very handy! In Railo 3.1 it is possible to write custom tags with components - I haven't had a chance to look into how that works yet, but it might make them even more flexible...
    rip747 : if not using an MVC framework, this is the tried and true method. used it numerous times in my career and also for smaller sites.
  • ColdFusion developers started using a custom tag called cf_bodycontent in the late 90s to avoid having to include separate header and footer files. That was six or seven years before ASP.NET's Master Pages. ;-)

    Now there's a native tag that does the same thing: cfsavecontent. Here's the essence of how people use cfsavecontent in templates.

       <!--- index.cfm --->
       <cfsavecontent variable="content">
          <cfinclude template="#url.action#.cfm">
       </cfsavecontent> 
    
       <cfinclude template="template.cfm">
    
       <!--- template.cfm --->
       <cfparam name="title" default="Welcome">
       <html>
          <head>#title#</head>
          <body>
             ... header, menu, sidebar, whatever ...
             <cfoutput>#content#</cfoutput>
             ... right column, footer ...
          </body>
       </html>
    
       <!--- foo.cfm --->
       <cfset title="Welcome to Foo">
       Hello World! I'm the page at index.cfm?action=foo
    
       <!--- bar.cfm --->
       <cfset title="Welcome to Bar">
       Hello World! I'm the page at index.cfm?action=bar
    

    If you want put a template within a template, just add another cfsavecontent.

       <!--- index.cfm --->
       <cfsavecontent variable="content">
          <cfinclude template="#url.action#.cfm">
       </cfsavecontent> 
    
       <cfsavecontent variable="content">
          <cfinclude template="internal_template.cfm">
       </cfsavecontent>
    
       <cfsavecontent variable="content">
          <cfinclude template="master_template.cfm">
       </cfsavecontent>         
    
       <cfoutput>#content#</cfoutput>
    

    You could refactor to cut out the redundancy.

       <!--- index.cfm --->
       <cfsavecontent variable="content">
           <cfinclude template="#url.action#.cfm">
       </cfsavecontent> 
    
       <cfparam name="templates" default="internal,master">
    
       <cfloop list="#templates#" index="t">
           <cfsavecontent variable="content">
               <cfinclude template="#t#_template.cfm">
           </cfsavecontent>
       </cfloop> 
    
       <cfoutput>#content#</cfoutput>
    

    If you want to have one template "extend" another, you could maybe do so by turning the list into a stack, and having each template push its parent onto the stack.

      <!--- internal_template.cfm --->
      <cfset templates = listAppend("master", templates)>  
    
      ...
      <cfoutput>#content#</cfoutput>
      ...
    
    
      <!--- index.cfm --->
      <cfsavecontent variable="content">
          <cfinclude template="#url.action#.cfm">
      </cfsavecontent> 
    
      <cfparam name="templates" default="internal">
    
      <cfloop condition="listlen(templates) gt 0">
          <cfset t = listFirst(templates)>
          <cfset templates = listRest(templates)>
          <cfsavecontent variable="content">
              <cfinclude template="#t#_template.cfm">
          </cfsavecontent>
      </cfloop> 
    
      <cfoutput>#content#</cfoutput>
    

    And thus you have StackBox, a ColdFusion framework invented on StackOverflow. :-)

    Joshua Carmody : Oooohhh, I like this too! Maybe I'll do it this way. +1

0 comments:

Post a Comment