Menu

As simple as possible, as complex as necessary

Generating page fragments in FW/1

8 August 2011

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.

controllers/main.cfc
<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.

controllers/main.cfc
<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.

Posted on . Updated

Comments

  • Formatting comments: See this list of formatting tags you can use in your comments.
  • Want to paste code? Enclose within <pre><code> tags for syntax higlighting and better formatting and if possible use script. If your code includes "self-closing" tags, such as <cfargument>, you must add an explicit closing tag, otherwise it is likely to be mangled by the Disqus parser.
Back to the top