My FB3 Lite

I was talking to Sandy Clark back at CFUNITED about how I still use a stripped-down FB3 for a lot of projects. She said I should publicize it, so here goes. At it's heart, it's a toolset for designing something that resembles a single-circuit FB3 application, but with some of the niceties (like DO) of FB4/5. I don't have a sample application to show at the moment, but I thought I'd share the framework itself (all 61 lines of it). It's inline at the end of the post, along with a sample switch file.

To use it, you just need to save the code as 'index.cfm' (or whatever) create a 'fbx_Switch.cfm' file in the same directory that has a CFSWITCH block that switches on 'attributes.currentFuseaction'. There are two mandatory CFCASEs it must contain: onRequestStart and onRequestEnd. The do exactly what you'd expect. Finally, the default fuseaction is named "__default_fuseaction__" (two underscores on the ends), so you'll want to define a CFCASE with that value as well, probably multi-aliased to something a little more friendly (like "home"). That's enough to get code executing.

The "tooling" that the framework exposes is quite simple, and I'll sum it up in a few bullet points:

  • a 'self' variable, which is set to "?#fuseactionVariable#=" and can be used for links (e.g. <a href="#self##xfa.logout#">)
  • an 'attributes.originalFuseaction' variable storing the requested fuseaction (or the default if none was specified)
  • an 'attributes.currentFuseaction' variable storing the currently executing fuseaction
  • a 'do' UDF that accepts a fuseaction to execute, and an optional variable name to put the execution output into
  • an 'include' UDF that accepts a template root to execute, and an optional variable name to put the execution output into
  • a 'location' UDF that accepts a destination URL and does a redirect via a HTTP Location header

Content variables for both the 'do' and the 'include' UDFs are append-only, so if you want to overwrite, you have to manually do it with a CFSET in your switch. Also as you'd expect, this is single-directory centric; index.cfm and fbx_Switch.cfm must be in the same directory, and includes must be pathed relative to that directory.

Why did I build such a framework? Mostly because neither FB3 nor FB5 fits the bill for small applications. Both are a bit on the heavy side, with FB5 being the heavier of the two. But at the same time, they both provide some really nice features (again, FB5 providing a larger number). So I wanted the best of both worlds, and I think I got it. My biggest gripe is that the single-directory nature makes it hard to clearly delineate the app from the framework, but that's a relatively minor issue I've found.

While I think any smaller application could use the framework without too many complaints, it does lend itself to apps where the total number of fuseactions is fairly small (duh), you don't want to do MVC inside the front controller (i.e. you want to use a separate backend), you want the dynamic assembly of FB5 (content variables, DOs and dynamic DOs, etc.), and you prefer to do your controller coding in CFML.

Without further ado, here's the code. I'll admit it seems rather dense, particularly on the web without syntax highlighting. But it's really pretty straightforward, and you probably won't ever need to open the file after you save it if you're using it.

<cfsetting enablecfoutputonly="true" />

<cfset fuseactionVariable = "do" />
<cfset defaultFuseaction = "__default_fuseaction__" />
<cfset self = "?#fuseactionVariable#=" />

<cfif NOT isDefined("attributes") OR NOT isStruct(attributes)>
  <cfset attributes = structNew() />
</cfif>
<cfset structAppend(attributes, url) />
<cfset structAppend(attributes, form) />

<cffunction name="do">
  <cfargument name="fuseaction" type="string" required="true" />
  <cfargument name="contentVariable" type="string" required="false" />
  <cfset var oldFuseaction = attributes.currentFuseaction />
  <cfset attributes.currentFuseaction = fuseaction />
  <cfif structKeyExists(arguments, "contentVariable")>
    <cfparam name="#contentVariable#" default="" />
    <cfsavecontent variable="#contentVariable#"><cfoutput>#variables[contentVariable]#</cfoutput>
      <cfinclude template="fbx_Switch.cfm" />
    </cfsavecontent>
  <cfelse>
    <cfinclude template="fbx_Switch.cfm" />
  </cfif>
  <cfset attributes.currentFuseaction = oldFuseaction />
</cffunction>

<cffunction name="include">
  <cfargument name="template" type="string" required="true" />
  <cfargument name="contentVariable" type="string" required="false" />
  <cfif listFind("cfm,cfml,htm,html", listLast(template, ".")) EQ 0>
    <cfset template = template & ".cfm" />
  </cfif>
  <cfif structKeyExists(arguments, "contentVariable")>
    <cfparam name="#contentVariable#" default="" />
    <cfsavecontent variable="#contentVariable#"><cfoutput>#variables[contentVariable]#</cfoutput>
      <cfinclude template="#template#" />
    </cfsavecontent>
  <cfelse>
    <cfinclude template="#template#" />
  </cfif>
</cffunction>

<cffunction name="location" output="false">
  <cfargument name="destUrl" type="string" required="true" />
  <cfheader statuscode="302" statustext="Object Temporarily Moved" />
  <cfheader name="Location" value="#destUrl#" />
</cffunction>

<cfparam name="attributes.#fuseactionVariable#" default="" />
<cfset attributes.fuseaction = attributes[fuseactionVariable] />
<cfif len(trim(attributes.fuseaction)) EQ 0>
  <cfset attributes.fuseaction = defaultFuseaction />
</cfif>
<cfset attributes.originalFuseaction = attributes.fuseaction />
<cfset attributes.currentFuseaction = attributes.fuseaction />

<cfset do("onRequestStart") />
<cfset do(attributes.currentFuseaction) />
<cfset do("onRequestEnd") />

And the sample fbx_Switch.cfm:

<cfswitch expression="#attributes.currentFuseaction#">

  <!--- lifecycle fuseactions --->

  <cfcase value="onRequestStart">
  </cfcase>

  <cfcase value="onRequestEnd">
  </cfcase>

  <!--- normal fuseactions --->

  <cfcase value="home,__default_fuseaction__">
    <cfset include("dsp_home", "bodyContent") />
    <cfset do("lay_default") />
  </cfcase>

  <!--- private fuseactions --->

  <cfcase value="lay_default">
    <cfset xfa.home = "home" />
    <cfset xfa.login = "loginForm" />
    <cfset include("lay_default") />
  </cfcase>

  <cfdefaultcase>
    <cfthrow type="IllegalFuseactionException"
      message="The specified fuseaction (#attributes.currentFuseaction#) is unknown." />
  </cfdefaultcase>

</cfswitch>

Edit: Here's a link to the project page.

6 responses to “My FB3 Lite”

  1. Kris Brixon

    Thanks for sharing. I agree that the full FB is a bit much for small apps. I have tried to slim down FB and was never really happy with the results. I will give this one a try.

  2. Kris Brixon

    The following is a link with sample files of the FB3 Lite project and a short video implementing it.

    http://www.mediafire.com/?2e2mz212yod
    Look for the "Click here to start download.."

  3. Kris Brixon

    No sound in the movie. It had been a while since I did a screen cast and since it took no time to get a working app from the framework, I thought I would record it and see how it goes. Recording a Virtual Machine window did not work out like I planned. The WMV Screen Cast codec did not render well, so I encoded as MOV at 50% size.

  4. Kris Brixon

    Update:
    I used this framework for a recent application (7000 LOC) that just went into production and it has worked out great. It was simple and easy to follow, it provided all the essential parts of FuseBox, but without the weight. It is also small enough to maintain on my own if Barney never has any updates to it.

    I started the application, but due to other priorities, I had to hand off the application to another programmer at the 5000 LOC mark and it did not take the other programmer any time to understand and start using this framework.

    Thanks, I appreciate you sharing your code.

  5. FB3 Lite Updates at BarneyBlog

    [...] mods I've made to the framework.  The core functionality is unchanged, so for background, check my original post from last year.  The enhancements, in no particular order, are as [...]