Generating page fragments in FW/1
Over the years I've moved towards splitting up my code into ever smaller, more focussed chunks allowing it to be maintained and re-used much more easily.
Generally speaking this means separating out blocks of logic and queries into CF components and functions on the one hand, and templates for outputting content to the browser on the other. A good MVC framework will then help tie the pieces together to fulfil each page request.
As well as chopping up logic into discrete CFCs and functions, I've also got into the habit of identifying portions of display templates which are being repeated in multiple places and putting them into their own single template that can be output wherever needed.
Simply including the template (using cfinclude
) is one way of outputting the fragment, but the downside is that it is then "buried" within the calling template. You have to look through the template code to know that there is a dependency on another included file.
The Fusebox way
With Fusebox I would instead always use the content variables feature to handle fragments. The fragment template is called in the controller circuit and its output saved to a request scoped variable which is then available to subsequent display or layout fuses. I would also document the variable as an "input" to the display fuse, just as argument declarations document a function's requirements.
circuit.xml<fuseaction name="page">
<include circuit="views" template="fragments/menu" contentVariable="request.content.menu"/>
<include circuit="views" template="page"/>
</fuseaction>
views/fragments/menu.cfm<cfoutput>
<nav><ul><li>Item 1</li><li>Item 2</li><li>Item 3</li></ul></nav>
</cfoutput>
views/main/page.cfm<!---EXPECTS
string:
request.content.menu
--->
<cfoutput>
<!--- common fragment --->
#request.content.menu#
<!--- page specific content --->
Some content
</cfoutput>
This makes the dependency much more transparent: I can see it both in the controller - the "road map" of the request - and at the top of the view template.
The FW/1 way
Having moved to FW/1 my fragments are now managed using the view( viewPath )
method which calls the template you specify. Until version 2.0, though, you could only use view()
from inside the view or layout template where you wanted the fragment to appear: effectively it's just an include.
<cfcomponent><cfscript>
// constructor - access to FW/1 API:
function init( fw )
{
variables.fw = arguments.fw;
}
//action=main.page
function page( rc ){
// views/main/page.cfm is called by convention
}
</cfscript></cfcomponent>
views/main/page.cfm<cfoutput>
<!--- common fragment --->
#view( "fragments/menu" )#
<!--- page specific content --->
Some content
</cfoutput>
Strict MVC
The reason for this limitation is FW/1's strong focus on the MVC "lifecyle": not only should the Model, View and Controller be separate sets of code, the Controller and View should, it seems, also be executed as separate "phases": the controller phase sets up the data for the request, the view phase then renders the output to the browser. Controllers, therefore, should have no need to access view templates, which belong in the rendering phrase.
But is capturing views as fragments in the controller really the same as "rendering"? Nothing is being rendered to the client when you save the output of a view template: the controller is just marshalling data - a string - just as it would call for an array or query to be passed further down the request.
I found I really couldn't do without the ability to capture display templates as variables (for other purposes too, not just fragments) so as previously mentioned I extended the framework with a simple custom method to achieve this.
FW/1 Fusebox style
As of FW/1 version 2.0 (now in alpha), however, view()
can now be used to capture views from the controller.
<cfcomponent><cfscript>
// constructor - access to FW/1 API:
function init( fw )
{
variables.fw = arguments.fw;
}
//action=main.page
function page( rc )
{
rc.content.menu = variables.fw.view( "fragments/menu" );
// views/main/page.cfm is called by convention
}
</cfscript></cfcomponent>
views/main/page.cfm<!---EXPECTS
string:
rc.content.menu
--->
<cfoutput>
<!--- common fragment --->
#rc.content.menu#
<!--- page specific content --->
Some content
</cfoutput>
Certain restrictions still apply if you use the framework to manage your services, but since I don't I'm now free to use this technique in this and a variety of other cases where it seems to provide a clear, practical solution with no disadvantages that I can discern. I may look at more of these in future posts.
Comments