How to structure a master page with coldfusion?
Asked Answered
W

3

7

I have a small coldfusion section of our site that all uses similar js and css files and page structure. The code is currently repeated for each file, and I'd like to factor it out and set something up using a master page and templates.

Master.cfm page:

<!--- Master template, includes all necessary js and css files. 
    Expects following variables to be defined:
    - pageName - name of the file to be loaded as the body of the page 
    - title - text to be used as the title of the page and as the header text in the header bar --->
<cfinclude template="_lockedPage.cfm" />

<!DOCTYPE HTML>
<html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
        <title>#title#</title>
        ... all script and css links here ...
        <script type="text/javascript" src="js/jquery-1.9.1.min.js"></script>
        <script type="text/javascript" src="js/jquery.mobile-1.3.2.js"></script>
        ... etc ...
    </head>
    <body>
        <div data-role="page">
            <div class="headerDiv" data-role="header" data-theme="b" data-position="fixed">
                <a id="backButton" data-role="button" data-direction="reverse" data-rel="back" data-icon="arrow-l" data-iconpos="left" data-theme="a">Back</a>
                <h1><cfoutput>#title#</cfoutput></h1>
                <a href="index.cfm" data-role="button" data-icon="home" data-iconpos="left" data-theme="a">Home</a>
            </div>
            <div data-role="content" class="container">
                <cfinclude template="#pageName#.cfm" />
            </div>
        </div>
    </body>
</html>

Then a page example would be something like this. CustomerSearch.cfm:

<cfscript>
    title = "Customer Search";
    pageName = "_customer-search";
    include "Master.cfm";
</cfscript>

And then I would need a _customer-search.cfm page that would include all the body content for the page.

This means that I would need 2 files for every page that we currently have - the outer page that defines the variable and includes the master page, and the template page that has the individual page content.

Is this a good logical structure? Is there anyway to improve it?

Weakling answered 20/3, 2014 at 15:18 Comment(0)
V
10

You have the right idea, but I think you'll end up with a lot of unnecessary files. You could instead create a header.cfm and a footer.cfm that contain your global HTML. Each page would include those files and the content would be written between.

<cfset title = "Customer Search">
<cfinclude template="global_header.cfm">

<!--- This will be the content of your page. --->

<cfinclude template="global_footer.cfm">

This file would be named customer_search.cfm. Anytime you update the header or footer, it's a global change.

If you have a lot of business logic and query code that needs to exist on multiple pages, you might look into using an MVC framework to help you organize and reuse code. I prefer ColdBox (try ColdBox Lite), but many people use Framework/1.

Vitia answered 20/3, 2014 at 16:3 Comment(3)
The includes for the two global files could be called from onRequestStart() and onRequestEnd().Palacio
@DanBracuk yes they could, but then you couldn't change the title of each page in the manner described.Vitia
Thanks, that would work. Doesn't feel as contained though; there will be opening tags in the header that are closed in the footer. And the "footer" will just contain ` </div> </div> </body> </html>`. But it does eliminate the need for extra files...Weakling
F
2

I found that using custom tags was another simple solution and in my opinion a better solution to creating a separate header.cfm and footer.cfm.

In master.cfm:

<cfif ThisTag.ExecutionMode EQ 'start'>
  [HEADER]
<cfelse>
  [FOOTER]
<cfif>

In each content page:

<cf_master>
  [CONTENT GOES HERE]
</cf_master>

If you'd like to pass in variables to the master page simply add it as an attribute to the opening tag:

<cf_master Title="Content Title">

And make sure the attribute is specified in the master file:

<cfparam name="Attributes.Title" default=""/>
<head>
  <title><cfoutput>#Attributes.Title#</cfoutput></title>
</head>

The key for me was understanding the ThisTag.ExectuionMode. If you use custom tags you can either use just one tag or use an opening and closing tag. If you use an opening and closing tag then you can choose to include some content in the opening tag <cf_master>, and other content in the closing tag </cf_master>. That is why you need the if/else condition in master.cfm. In this case it is useful because then you can include a HEADER in the opening tag and a FOOTER in the closing tag.

Also, in case this isn't obvious, when you call your custom tag, it should match the name of the file where the code is stored. In my case <cf_master> matches master.cfm.

I used this page as a tutorial for custom tags: https://www.petefreitag.com/item/64.cfm

Frijol answered 4/12, 2015 at 2:54 Comment(0)
S
0

The Application.cfc can be a great use for common page design. Basically have one template and inject the pages generated content. Dan Bracuk commented in the other solution about using the Application.cfc onRequestStart() and onRequestEnd() methods but I use it slightly differently. Here is my general setup:

Application.cfc

// This is <cfscript> but it could be regular CFML too
component {
    public function onRequest( required string targetPage ) {

        // Capture/buffer the requested pages output
        savecontent variable='LOCAL.output' {
            include ARGUMENTS.targetPage;
        }


        // Use the output as the page content
        // if the page did not specify content
        param string REQUEST.content = LOCAL.output;


        // Inject the design template
        // which should output the page content somewhere
        include '/path/to/template.cfm';
    }
}

template.cfm

<!DOCTYPE html>
<cfparam name="REQUEST.title"   type="string" /><!--- required --->
<cfparam name="REQUEST.head"    type="string" default="" />
<cfparam name="REQUEST.content" type="string" /><!--- required --->
<html>
    <head>
        <title><cfoutput>#REQUEST.title#</cfoutput></title>
        <link rel="stylesheet" href="path/to/common.css" />
        <script src="path/to/common.js"></script>
        <cfoutput>#REQUEST.head#</cfoutput>
    </head>
    <body>
        <header>...</header>
        <cfoutput>#REQUEST.content#</cfoutput>
        <footer>...</footer>
    </body>
</html>

each-page.cfm

<cfset REQUEST.title = "My Page Title" />


<cfsavecontent variable="REQUEST.head">
    <!-- page specific head elements here -->
</cfsavecontent>


<!-- Regular page code/HTML output here -->
<!--- or you could use another <cfsavecontent> block --->
<!--- to save specific output sections --->
<p>Hello World</p>

This way allows you to keep the template all within one file which is much easier when designing it in a WYSIWYG manor. It also allows each page to set variables used in the design template, since the requested page is executed before the design template is included.

And, there is no need to <cfinclude> templates on each page since Application.cfc onRequest() will get called for ALL pages by default. If there are .cfm pages which should NOT include the design template, such as PDF output, then you'll need to add some logic to just dump the output and not include the design template.

Selassie answered 20/3, 2014 at 19:22 Comment(9)
"The Application.cfc is a great use for common page design". No, really it's not. And to suggest having page-specific CSS and JS in a CFM file? Very poor advice. The correct advice ought to be "use a lightweight framework like FW/1". Downvoted.Redfin
I disagree with your view. ColdFusion can be used for basic apps where frameworks such as FW/1 are just overkill and adds unneeded complexity. As for page specific CSS/JS, I've updated my answer to demonstrate a more global approach. My answer is still a valid option, it's up to the user to decide which is best for their case. Please don't down vote because of your own development styles, down vote because it's not a workable solution.Selassie
Saying that "frameworks such as FW/1 are just overkill" suggests you don't know FW/1 - it is very simple; the overhead/structure is arguably less complex to what you're doing here, but with the significant advantage that other people don't need to figure out what you're doing because FW/1 provides a standard.Berm
@PeterBoughton Fair enough, I don't know all that much about FW/1 specifically but I personally haven't felt the need for anything more than my answer for my most basic apps. If the developer feels their project would benefit from a framework, then by all means, use a framework. But to suggest that frameworks are always require is just ridiculous. And my solution is easy to understand what is going on, just take a look at the Application.cfc onRequest() method.Selassie
The point is: there are numerous other developers that feel exactly like you. Each of them have their own versions of what you have written, a simple structure that they consider easy to understand. And every single one of these IS A FRAMEWORK - albeit undocumented and non-standardised.Berm
FW/1 is effectively the same solution as all of these individual frameworks but standardised and documented (and with optional hooks for more advanced stuff) - so everyone knows where they are when using it, and doesn't have to waste time (no matter how little) identifying how it works. Put another way: There is no time when using your proposed solution would be superior to FW/1 for anyone other than yourself.Berm
Also, to make it clear, whilst at no point have I said a framework is required, for anything beyond single simple scripts, a framework of some kind is almost always a good idea - because the alternative (no framework) means it's completely unpredictable how things are structured, and thus a nightmare to maintain an application of any significant size.Berm
Now whether the framework used is a standard off-the-shelf one (FW/1, Coldbox, cfWheels, etc) or whether it's something peculiar to the needs of a specific application - that's something to consider for each application being built. In most cases, starting with a standard framework is the best option, because the very fact that important questions have already been considered and solved/documented makes it easier to work with and faster to get on with completing the important parts of an application (without re-inventing wheels unnecessarily).Berm
First of all, I can't believe how big of a discussion this has become between a couple people. Now, I'm not against you in the use of frameworks. In fact, structure is exactly why I chose Ember JS over Angular JS for my SPA's. Structure is very important, I've inherited some structureless code myself (a pain). At least my solution provides some structure. And to end, all I'm saying is that ColdFusion is meant for rapid development and provides you with a basic framework using Application.cfc. How to use it beyond that is up to the developer (extend Application.cfc or use another framework).Selassie

© 2022 - 2024 — McMap. All rights reserved.