OO and Fusebox no xml

September 18, 2008

Brian Kotek wrote a sample application which illustrates extensive use of OO principles. I've taken Brian's Fusebox 4.1 Bookstore code and reworked it to use Fusebox 5.5 no XML. Thanks go to Brian for letting me post his code :)

You can download Brian's original Fusebox 4.1 Bookstore Sample Application from the Fusebox.org website.

I didn't touch the model only the Fusebox specific stuff and it all works nicely. Brian's original code is not meant to be a production application just an skeleton demonstration, so I've gone along the same lines and tried to keep it simple. These are the steps I took:

Here is Brian's fusebox.init.cfm (the Application.cfm doesn't really do anything)

<cfsilent>
<!--- Initialize applicaiton manager components. --->
<cfparam name="url.appReload" type="string" default="false" />
<cfif not structKeyExists( application, 'appInitialized' ) or url.appReload>
  <cflock name="appInitBlock" type="exclusive" timeout="10">
    <cfif not structKeyExists( application, 'appInitialized' ) or url.appReload>
      <cfset utility = createObject( 'component', 'fb41bookstore.com.utility' ).init() />
      <cfset application.orderManager = createObject( 'component', 'fb41bookstore.com.orderManager' ).init( utility ) />
      <cfset application.productManager = createObject( 'component', 'fb41bookstore.com.productManager' ).init( utility ) />
      <cfset application.cartManager = createObject( 'component', 'fb41bookstore.com.cartManager' ).init() />
      <cfset application.appInitialized = true />
    </cfif>
  </cflock>
  <cfset structClear( session ) />
</cfif>

<!--- Set app constants. --->
<cfset self = "index.cfm">

<cfset mySelf = "#urlSessionFormat( '#self#' )#" />

<!--- Workaround for IIS problem with JSessionID: http://www.macromedia.com/cfusion/knowledgebase/index.cfm?id=1c6b723  --->
<cfif findNoCase( 'index.cfm;', mySelf )>
  <cfset mySelf = replace( mySelf, 'index.cfm;', 'index.cfm?' ) />
</cfif>

<cfif right( mySelf, 9 ) eq "index.cfm">
  <cfset mySelf = mySelf & "?" />
<cfelse>
  <cfset mySelf = mySelf & "&" />
</cfif>

<cfset mySelf = mySelf & "#application.fusebox.fuseactionVariable#=" />
</cfsilent>

Here is the new Application.cfc

<cfcomponent extends="fusebox5.Application" output="false">

  <cfset this.name = Hash(getDirectoryFromPath(getCurrentTemplatePath())) />
    <cfset this.sessionmanagement = true />
  <cfset this.sessiontimeout = CreateTimeSpan(0,0,20,0) />
  
  <!--- trap non-index.cfm requests - must be outside onXxxYyy() handlers --->
  <cfif right(cgi.script_name, len("index.cfm")) neq "index.cfm" and right(cgi.script_name, 3) neq "cfc">
    <cflocation url="index.cfm" addtoken="false" />
  </cfif>
  
  <cfscript>
  // must enable implicit (no-XML) mode!
  FUSEBOX_PARAMETERS.allowImplicitFusebox = true;
  FUSEBOX_PARAMETERS.defaultFuseaction = "store.main";
  // you may want to change this to production mode:
  FUSEBOX_PARAMETERS.mode = "development-full-load";
  FUSEBOX_PARAMETERS.conditionalParse = true;
  // change this to something more secure:
  FUSEBOX_PARAMETERS.password = "";
  FUSEBOX_PARAMETERS.strictMode = true;
  FUSEBOX_PARAMETERS.debug = false;
  // we use the core file error templates:
  FUSEBOX_PARAMETERS.errortemplatesPath = "/fusebox5/errortemplates/";

  // These are all default values that can be overridden:
  // FUSEBOX_PARAMETERS.fuseactionVariable = "fuseaction";
  // FUSEBOX_PARAMETERS.precedenceFormOrUrl = "form";
  // FUSEBOX_PARAMETERS.scriptFileDelimiter = "cfm";
  // FUSEBOX_PARAMETERS.maskedFileDelimiters = "htm,cfm,cfml,php,php4,asp,aspx";
  // FUSEBOX_PARAMETERS.characterEncoding = "utf-8";
  // FUSEBOX_PARAMETERS.strictMode = false;
  // FUSEBOX_PARAMETERS.allowImplicitCircuits = false;  
  </cfscript>
  
  <!---
    if you define any onXxxYyy() handler methods, remember to start by calling
      super.onXxxYyy(argumentCollection=arguments)
    so that Fusebox's own methods are executed before yours
  --->
  <cffunction name="onApplicationStart" 
    returntype="boolean" 
    output="false" 
    access="public" 
    hint="I am executed when the application starts">
    
    <cfset super.onApplicationStart() />
    
    <cfset utility = createObject( 'component', 'fb55bookstore.com.utility' ).init() />
    <cfset application.orderManager = createObject( 'component', 'fb55bookstore.com.orderManager' ).init( utility ) />
    <cfset application.productManager = createObject( 'component', 'fb55bookstore.com.productManager' ).init( utility ) />
    <cfset application.cartManager = createObject( 'component', 'fb55bookstore.com.cartManager' ).init() />
    <cfset application.appInitialized = true />
    
    <cfreturn True />
  </cffunction>

  <cffunction name="onFuseboxApplicationStart">
    <cfset super.onFuseboxApplicationStart() />
  
    <!--- replaces fusebox.appinit.cfm --->
    <cfset myFusebox.getApplicationData().startTime = Now() />
  
  </cffunction>
  
  <cffunction name="onRequestStart" 
    returntype="boolean" 
    output="false" 
    access="public" 
    hint="I am executed at the atart of each request">
    
    <cfargument name="targetPage" 
      type="string" 
      required="true" />
    
    <cfset super.onRequestStart(arguments.targetPage) />
    
    <cfif StructKeyExists(URL, "appReload")>
      <!--- reinitialise the application scope --->
      <cfset onApplicationStart() />
    </cfif>
    
    <cfset tickBegin = GetTickCount()>
    
    <!--- formerly in fusebox.init.cfm --->
    <cfset self = myFusebox.getSelf() />
    <cfset myself = myFusebox.getMyself() />
    
    <cfreturn True />
  
  </cffunction>

  <cffunction name="onRequestEnd" 
    returntype="void" 
    output="true" 
    access="public" 
    hint="I am executed when all pages in the request have finished processing">
    <cfargument name="targetPage" 
      type="string" 
      required="true" />

    <!--- Pass request to Fusebox. --->
    <cfset super.onRequestEnd(arguments.targetPage) />
    
    <!--- formerly in onRequestEnd.cfm --->
    <cfset tickEnd = GetTickCount()>
    <cfset executionTime = tickEnd - tickBegin>
    <cfoutput>executionTime: #executionTime# ms</cfoutput>
  </cffunction>

</cfcomponent>

Here is Brian's Controller (an XML circuit )

<circuit access="public">
  
  <fuseaction name="main">
    <set name="xfa.productDetails" value="store.productDetails"/>
    <invoke object="application.productManager" methodcall="getFeaturedProducts()"
        returnVariable="getFeaturedProducts" />
    <do action="v_pages.featuredProducts" contentvariable="pageContent" />
    <do action="createCartSidebar" />
    <do action="createRecentOrdersSidebar" />
    <do action="createRecentlyViewedSidebar" />    
  </fuseaction>

  <fuseaction name="productDetails">
    <set name="xfa.addtocart" value="store.addproducttocart"/>
    <invoke object="application.productManager" methodcall="getProductById( attributes.productID )"
        returnVariable="product" />
    <do action="v_pages.productDetails" contentvariable="pageContent"/>
    <do action="createRelatedProductsSidebar" />    
    <do action="createCartSidebar" />
    <do action="createRecentOrdersSidebar" />
    <do action="createRecentlyViewedSidebar" />
  </fuseaction>
  
  <fuseaction name="createCartSidebar" access="private">
    <invoke object="application.cartManager" methodcall="getCart()"
        returnVariable="cart" />  
    <do action="v_sidebarBlocks.cartSummary" contentvariable="cartSummary"/>
  </fuseaction>
  
  <fuseaction name="createRecentOrdersSidebar" access="private">
    <invoke object="application.orderManager" methodcall="getOrderUpdate()"
        returnVariable="getOrderUpdate" />    
    <do action="v_sidebarBlocks.orderUpdate" contentvariable="orderUpdate"/>
  </fuseaction>
  
  <fuseaction name="createRecentlyViewedSidebar" access="private">
    <set name="xfa.productdetails" value="store.productDetails"/>
    <invoke object="application.productManager" methodcall="getRecentlyViewedProducts()"
        returnVariable="getRecentlyViewedProducts" />  
    <do action="v_sidebarBlocks.recentlyViewedProducts" contentvariable="recentlyViewedProducts"/>
  </fuseaction>
  
  <fuseaction name="createRelatedProductsSidebar" access="private">
    <set name="xfa.productdetails" value="store.productDetails"/>
    <invoke object="application.productManager" methodcall="getRelatedProducts( attributes.productID )"
        returnVariable="getRelatedProducts" />
    <do action="v_sidebarBlocks.relatedProducts" contentvariable="relatedProducts"/>  
  </fuseaction>
  
  <fuseaction name="viewCart">
    <set name="xfa.continue" value="store.main"/>
    <set name="xfa.clearcart" value="store.clearcart"/>
    <set name="xfa.productdetails" value="store.productDetails"/>
    <set name="xfa.changequantity" value="store.changecartquantity"/>
    <set name="showSidebar" value="false"/>
    <invoke object="application.cartManager" methodcall="getCart()"
        returnVariable="cart" />  
    <do action="v_pages.cartDetails" contentvariable="pageContent"/>
  </fuseaction>
  
  <fuseaction name="addProductToCart">
    <set name="xfa.continue" value="store.main"/>
    <set name="xfa.clearcart" value="store.clearcart"/>
    <set name="xfa.productdetails" value="store.productDetails"/>
    <set name="xfa.changequantity" value="store.changecartquantity"/>
    <set name="showSidebar" value="false"/>
    <invoke object="application.productManager" methodcall="getProductById( attributes.productID )"
        returnVariable="product" />
    <invoke object="application.cartManager" methodcall="addToCart( product )" />  
    <invoke object="application.cartManager" methodcall="getCart()"
        returnVariable="cart" />  
    <do action="v_pages.cartDetails" contentvariable="pageContent"/>
  </fuseaction>
  
  <fuseaction name="changeCartQuantity">
    <set name="xfa.continue" value="store.main"/>
    <set name="xfa.clearcart" value="store.clearcart"/>
    <set name="xfa.productdetails" value="store.productDetails"/>
    <set name="xfa.changequantity" value="store.changecartquantity"/>
    <set name="showSidebar" value="false"/>
    <invoke object="application.cartManager" methodcall="setQuantity( attributes.productID, attributes.quantity )" />  
    <invoke object="application.cartManager" methodcall="getCart()"
        returnVariable="cart" />  
    <do action="v_pages.cartDetails" contentvariable="pageContent"/>
  </fuseaction>
  
  <fuseaction name="clearCart">
    <set name="xfa.continue" value="store.main"/>
    <set name="showSidebar" value="false"/>
    <invoke object="application.cartManager" methodcall="clearCart()" />  
    <invoke object="application.cartManager" methodcall="getCart()"
        returnVariable="cart" />  
    <do action="v_pages.cartDetails" contentvariable="pageContent"/>
  </fuseaction>

</circuit>

Here is the Controller as a CFC (CodeHighlighter seems to messing this up so you might want to use the 'view plain' option)

<cfcomponent output="false"
  hint="I am the store circuit">
  
  <!---
  / ************************************************ / 
  / I've put the pre and post fuseactions in for
  / reference. They are not required.
  / ************************************************ /
  --->
  <!---
  <cffunction name="preFuseaction"
    access="public"
    output="false"
    hint="I am executed before each fuseaction">
    <cfargument name="myFusebox" />
    <cfargument name="event" />
  </cffunction>
   --->
  <cffunction name="main"
    access="public"
    output="false"
    hint="I am the main fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "productdetails", "productDetails" ) />
    
    <cfset event.setValue( "getFeaturedProducts", application.productManager.getFeaturedProducts() ) />

    <cfset myFusebox.do( action="v_pages.dsp_featuredProducts", contentvariable="pageContent" ) />
    
    <cfset myFusebox.do( action="createCartSidebar" ) />
    <cfset myFusebox.do( action="createRecentOrdersSidebar" ) />
    <cfset myFusebox.do( action="createRecentlyViewedSidebar" ) />
        
    <!--- build the layout --->
    <cfset myFusebox.do( action="layouts.buildPage" ) />
  </cffunction>
  
  <cffunction name="productDetails"
    access="public"
    output="false"
    hint="I am the productDetails fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "addtocart", "addproducttocart" ) />
    
    <cfset event.setValue( "product", application.productManager.getProductById( event.GetValue('productID') ) ) />

    <cfset myFusebox.do( action="v_pages.dsp_productDetails", contentvariable="pageContent" ) />
    <cfset myFusebox.do( action="createRelatedProductsSidebar" ) />
    <cfset myFusebox.do( action="createCartSidebar" ) />
    <cfset myFusebox.do( action="createRecentOrdersSidebar" ) />
    <cfset myFusebox.do( action="createRecentlyViewedSidebar" ) />
  </cffunction>
  
  <cffunction name="createCartSidebar"
    access="public"
    output="false"
    hint="I am the createCartSidebar fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <cfset event.setValue( "cart", application.cartManager.getCart() ) />

    <cfset myFusebox.do( action="v_sidebarBlocks.dsp_cartSummary", contentvariable="cartSummary" ) />
  </cffunction>
  
  <cffunction name="createRecentOrdersSidebar"
    access="public"
    output="false"
    hint="I am the createRecentOrdersSidebar fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <cfset event.setValue( "getOrderUpdate", application.orderManager.getOrderUpdate() ) />

    <cfset myFusebox.do( action="v_sidebarBlocks.dsp_orderUpdate", contentvariable="orderUpdate" ) />
  </cffunction>

  <cffunction name="createRecentlyViewedSidebar"
    access="public"
    output="false"
    hint="I am the createRecentlyViewedSidebar fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "productdetails", "productDetails" ) />
    
    <cfset event.setValue( "getRecentlyViewedProducts", application.productManager.getRecentlyViewedProducts() ) />

    <cfset myFusebox.do( action="v_sidebarBlocks.dsp_recentlyViewedProducts", contentvariable="recentlyViewedProducts" ) />
  </cffunction>

  <cffunction name="createRelatedProductsSidebar"
    access="public"
    output="false"
    hint="I am the createRelatedProductsSidebar fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "productdetails", "productDetails" ) />
    
    <cfset event.setValue( "getRelatedProducts", application.productManager.getRelatedProducts( event.GetValue('productID') ) ) />

    <cfset myFusebox.do( action="v_sidebarBlocks.dsp_relatedProducts", contentvariable="relatedProducts" ) />
  </cffunction>

  <cffunction name="viewCart"
    access="public"
    output="false"
    hint="I am the viewCart fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "continue", "main" ) />
    <cfset event.xfa( "clearcart", "clearcart" ) />
    <cfset event.xfa( "productdetails", "productDetails" ) />
    <cfset event.xfa( "changequantity", "changecartquantity" ) />
    
    <cfset event.setValue( "showSidebar", false ) />

    
    <cfset event.setValue( "cart", application.cartManager.getCart() ) />

    <cfset myFusebox.do( action="v_pages.dsp_cartDetails", contentvariable="pageContent" ) />
  </cffunction>
  
  <cffunction name="addProductToCart"
    access="public"
    output="false"
    hint="I am the addProductToCart fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <cfset var product = "" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "continue", "main" ) />
    <cfset event.xfa( "clearcart", "clearcart" ) />
    <cfset event.xfa( "productdetails", "productDetails" ) />
    <cfset event.xfa( "changequantity", "changecartquantity" ) />
    
    <cfset event.setValue( "showSidebar", false ) />

    <cfset product = application.productManager.getProductById( event.GetValue('productID') ) />
    <cfset application.cartManager.addToCart( product ) />
    <cfset event.setValue( "cart", application.cartManager.getCart() ) />

    <cfset myFusebox.do( action="v_pages.dsp_cartDetails", contentvariable="pageContent" ) />
  </cffunction>
  
  <cffunction name="changeCartQuantity"
    access="public"
    output="false"
    hint="I am the changeCartQuantity fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "continue", "main" ) />
    <cfset event.xfa( "clearcart", "clearcart" ) />
    <cfset event.xfa( "productdetails", "productDetails" ) />
    <cfset event.xfa( "changequantity", "changecartquantity" ) />
    
    <cfset event.setValue( "showSidebar", false ) />

    <cfset application.cartManager.setQuantity( event.GetValue('productID'), event.GetValue('quantity') ) />
    <cfset event.setValue( "cart", application.cartManager.getCart() ) />

    <cfset myFusebox.do( action="v_pages.dsp_cartDetails", contentvariable="pageContent" ) />
  </cffunction>
  
  <cffunction name="clearCart"
    access="public"
    output="false"
    hint="I am the clearCart fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <!--- 
    note: you don't need to prefix the circuit in the xfa 
    if the xfa is for a fuse in the same circuit 
    --->
    <cfset event.xfa( "continue", "main" ) />
    
    <cfset event.setValue( "showSidebar", false ) />

    <cfset application.cartManager.clearCart() />
    <cfset event.setValue( "cart", application.cartManager.getCart() ) />

    <cfset myFusebox.do( action="v_pages.dsp_cartDetails", contentvariable="pageContent" ) />
  </cffunction>
  
  <!---
  / ************************************************ / 
  / I've put the pre and post fuseactions in for
  / reference. They are not required.
  / ************************************************ /
  --->
  <!--- 
  <cffunction name="postFuseaction"
    access="public"
    output="false"
    hint="I am executed after each fuseaction">
    <cfargument name="myFusebox" />
    <cfargument name="event" />
  </cffunction>
  --->
  
</cfcomponent>

As we now don't have a Fusebox.xml.cfm file, I needed to find a way to replicate the globalfuseactions > postprocess to render the layout. To do this I decided that I should create a seperate circuit to handle the layouts and then call it from the onRequestEnd method of Application.cfc.

Layouts.cfc (CodeHighlighter seems to messing this up so you might want to use the 'view plain' option)

<cfcomponent output="true"
  hint="I am the layout controller">
    
  <cffunction name="buildPage"
    access="public"
    output="true"
    hint="I am the buildPage fuseaction">

    <cfargument name="myFusebox" />
    <cfargument name="event" />
    
    <cfset myFusebox.do( action="v_layouts.lay_menu", contentvariable="menuContent" ) />
    <cfset myFusebox.do( action="v_layouts.lay_sidebar", contentvariable="sidebarContent" ) />
    <cfset myFusebox.do( action="v_layouts.lay_htmllayout" ) />

  </cffunction>

</cfcomponent>

My onRequestEnd method now looks like this:

<cffunction name="onRequestEnd" 
  returntype="void" 
  output="true" 
  access="public" 
  hint="I am executed when all pages in the request have finished processing">
  <cfargument name="targetPage" 
    type="string" 
    required="true" />

  <cfoutput>#myFusebox.do( 'layouts.buildPage' )#</cfoutput>
    
  <!--- Pass request to Fusebox. --->
  <cfset super.onRequestEnd(arguments.targetPage) />
    
  <!--- formerly in onRequestEnd.cfm --->
  <cfset tickEnd = GetTickCount()>
  <cfset executionTime = tickEnd - tickBegin>
  <cfoutput>executionTime: #executionTime# ms</cfoutput>
</cffunction>

Finally, as we are using the event model to store our objects in, we need to retrieve them to use them in the display pages. This can be done really easily like this:

<cfset cart = event.getValue( "cart" ) />

You can download the source code as a zip.


No comments

Leave a comment

If you found this post useful, interesting or just plain wrong, let me know - I like feedback :)

Please note: If you haven't commented before, then your comments will be moderated before they are displayed.