If you haven't seen the introduction, start here.
All of the MVC frameworks that have been ported have a number of basic similarities. All of them prescribe a system for organizing different types of files that collectively make up an application, systems for controlling the logical flow of a page request, systems for declaring business logic, and systems for displaying content to the user. What these feautres boil down to mostly is encouraging good code reuse by encouraging highly cohesive and loosely coupled components.
Ultimately these frameworks may have more in common than they have unique features.
Please NOTE that I may use the terms "model" and "business objects" interchangably in this article.
All of these frameworks have some basic settings many of which are for common tasks or features. For example, all of these frameworks have some form of modality to switch between a "development" or "debugging" mode and a "production" mode for applications that are fully developed and deployed onto public servers. Of the five frameworks, the onTap framework is the only one that doesn't use XML for its essential configuration settings.
I have to assume that all of these file names are case-sensitive if you're deploying any of these frameworks on a unix-based operating system. In my case with the onTap framework I've deliberately forced the application to use all lowercase file names to eliminate any ambiguity about case-sensitivity.
You can read more about common framework application settings here.
Once the basic configuration for a given framework is done, the framework will prescribe some information about where different types of files ought to be placed. Usually this boils down mostly to the notion of separating files into Model, View and Controller (MVC) directories.
For those unfamiliar with the terminology, here's my 5-second explanation of MVC.
The basic premise of the entire theory or philosophy of MVC is that if you completely separate your business logic from your view (in our case mostly HTML), then the application will be much easier to maintain and modify as a result of containing less pasta. Hence Model and View and since never the twain shall meet, you need a third-party (the controller) to delegate messages between them.
Now lets start by looking at what the original Galleon application contained. Galleon's original directory structure looks like this:
/Galleon2/
/admin/
/Application.cfm
/conferences.cfm
/forums.cfm
/index.cfm
/login.cfm
/n...cfm
/cfcs/
/conference.cfc
/forum.cfc
/galleon.cfc
/message.cfc
/objectfactory.cfc
/n...cfc
/images/n...jpg
/includes/udf.cfm
/pagetemplates/
/admin_header.cfm
/admin_footer.cfm
/main_header.cfm
/main_footer.cfm
/stylesheets/
/style.css
/admin_style.css
/tags/
/breadcrumbs.cfm
/datatable.cfm
/layout.cfm
/pagination.cfm
/Application.cfm
/confirm.cfm
/denied.cfm
/forums.cfm
/index.cfm
/login.cfm
/n...cfm
It's not as intimidating as it might look.
The two Application.cfm templates (bold) contain mostly controller logic (primarily concerned with making sure the business objects are created and stored in the application scope for later use). The business logic (model) is all neatly sequestered in the cfcs directory (blue). The green templates are neatly separated parts of the view. The red templates on the other hand are... what any MVC guru will tell you never to do. The red templates are base templates which contain both controller logic and views, the traditional clueless noob's single-template ColdFusion "page". Of course, given that Ray contributed this to the open source community and wasn't using a predefined MVC framework, we can forgive him... this time. ;)
The reality is that Ray's structure isn't so horrible. In spite of the fact that he wasn't using an MVC framework to put this application together, he still mostly separated the code into the areas that go into an MVC architecture. Most importantly all his business logic is in his CFCs and that goes a long way toward making it possible to port Galleon to various different frameworks or potentially even different media, such as a Flash front-end.
So long-story short, here's where most of these files went in the ports (p.s. please email me if you can find the width-problem in the CSS below):
|
ColdBox
/cbx/ /handlers/ /ehForums.cfc /ehMessages.cfc /ehUsers.cfc /model/galleon/ /views/galleon/ /layouts/... Fusebox
/fbx/traditional/ /controller/... /model/galleon/ /view/ /display/galleon/... /layout/lay_forum.cfm Mach-II
/mii/ /config/galleon.xml /listeners/... /model/galleon/ /views/galleon/ |
Model-Glue
/glu/ /controller/forum.cfc /model/galleon/ /views/galleon/ onTap
/ontap/
/_components/
/_cfc/galleon/
/_includes/galleon/
/forum/
/_htmlhead/
/100_galleon.cfm
/galleon.css
/_layout.cfm
/confirm/_local/100_galleon.cfm
/denied/_local/100_galleon.cfm
/forums/_local/100_galleon.cfm
/n.../_local/100_galleon.cfm
/forum/
/confirm.cfm
/denied.cfm
/forums.cfm
/n...cfm
|
Although each framework handles the placement of these files a little differently, you should be able to see pretty easily from these samples that the differences aren't very dramatic.
Model: The application "skeleton" for all four frameworks aside from the onTap framework includes a /model/ directory by default, which is generally (as far as I know) where CFCs are placed. The onTap framework doesn't have a "skeleton" because the framework is all inside the application root directory. The other four frameworks use a skeleton to get you started with a new application because the framework core files are installed elsewhere on the server. The onTap framework is the only one of the five frameworks that uses a /_cfc/ directory instead of a /model/ directory.
Of the five, onTap and Fusebox are the only frameworks for which the path to model components has any special meaning. In Mach-II, Model-Glue and ColdBox the CFCs can be placed anywhere and nothing changes. While CFCs can be placed anywhere in an onTap or Fusebox application also, each framework offers some optional automation if you choose to use the default directories. Neither of these features really need to be explained here, just make a mental note to go find out more if they interest you.
Most important to note here is that none of Ray's CFCs needed to be modified in any way to accomodate any of these ports. All that was necessary was to place them and inform the application where to find them (which I did using an application-specific mapping in the Application.cfc for each ported application). That being said, I did modify Ray's components for the onTap framework port, not out of necessity but rather out of interest in analyzing the effect of the onTap framework's SQL Abstraction library.
View: Elements of the display of an application seem to be generally more difficult for people to separate from the controller and are likely to involve a few more changes from one framework to another, compared to the model components. In an ideal world of course, neither the model or the views would change, however, that's not generally the case.
The application skeleton for all four frameworks however (again, except the onTap framework) include a default /views/ directory to place templates (mostly HTML) for generating the content portion of your pages. In the onTap framework the designated place to put content templates is in the /_includes/ directory. In Mach-II and the onTap framework, this is purely a convenient way to group display templates. In Fusebox in addition to being common convention the framework makes use of this directory for some automation if implicit circuits are used. In the remaining two frameworks (ColdBox & Model-Glue), the framework requires that display templates be placed in the /views/ directory.
Ray also divided his view into two parts (content and layout) by using a custom tag for layout in the original Galleon application. Although this is a common practice and most applications ultimately have some form of layout which surrounds the content and binds it together (sort of like the Force), not all frameworks address the idea of layouts directly. While previous versions of Fusebox included specific features for "nested layouts" these have been removed since version 4. Currently only ColdBox and the onTap framework include specific core features for displaying layouts. However in the communities for all three remaining frameworks popular conventions have provided very similar alternatives. I've used what I perceive to be the common conventions for layouts with these frameworks, so ultimately even though they don't have layout "features", I've used the same layout for the forum in much the same way in all five frameworks.
Controller: This is where you're liable to find the most variation between frameworks, simply because when we write applications without frameworks (like Ray's original Galleon application) we tend not to differentiate much between the controller and the view (or in some cases the model, as can be seen in the way that some of Ray's model components include use of isUserInRole() which is taboo in an MVC architecture). We tend to learn the most about the model and the view early on in our careers and it's not until later that we start to develop an understanding of the notion of separation of concerns, which is where the controllers come in. So for the most part, these frameworks each seek largely to create a controller layer just to separate the model and view from each other. Controllers being the primary focus of frameworks, they have a tendancy to gloss over aspects of both the model and the view and focus heavily on defining application flow.
In the beginning, there was Fusebox and it was good... for a while. Fusebox defined application flow in a "hub and spoke" fashion, using a collection of "circuits" (admin, forum, blog, etc) and in each circuit was declared a series of "fuseactions" using a large CFSWITCH. This is how the framework determined which code to execute. All requests to the framework would be funelled through index.cfm, to which would be appended ?fuseaction=[circuit].[fuseaction]. The framework would then locate the requested circuit and execute the code in the requested fuseaction. So index.cfm?fuseaction=forum.messages would execute code in the "messages" fuseaction of the "forum" circuit.
Ultimately, this is very similar to the way most ColdFusion frameworks operate today. After the release of ColdFusion MX it became popular to use XML as the "language of choice" for defining "circuits" and/or "fuseactions". Speaking of language, that's another important part of what's changed. Fusebox had a "quirky" jargon and later frameworks have since adopted different names for things which are the same names you'll find used for them in languages other than ColdFusion like Java or C++. So in the newer frameworks, "circuits" have become "modules" in Mach-II, "Event-Handlers" in ColdBox and "Controllers" in Model-Glue. Similarly Fuseactions gave way to "events". I believe in the onTap framework documentation I refer to them as "processes" and the notion of "circuits" is a bit different, which I'll talk about more later when I get into events and event handlers.
The quick-start guide for Mach-II says "when you see the word EVENT, think PAGE". Although that's not the whole story, it's a good place to get started. I'll flesh it out a little further with this handy jargon translation table:
| Concept | ColdBox | Fusebox | Mach-II | Model-Glue | onTap |
| Page | event | fuseaction | event | event | process |
| Sub-Application | event-handler | circuit | module | controller * | ** |
| * A controller in Model-Glue is a little different than the conventional notion of a "circuit" or "sub-applciation" however, for the purposes of this comparison, it's close enough. | |||||
| ** There isn't really a defined jargon for describing the notion of "circuits" in the onTap framework - the framework supports a set of features similar to the circuits and parent-circuits defined by fusebox although with much more flexibility, but there isn't common terminology today for the concept of a "circuit" | |||||
So you can see with this table how in the picture to the right a "circuit" is basically just a way to organize a collection of related "events" (pages) in your application. In an application with no framework, this typically translates to a directory or "file folder". So for example, your forum is www.mysite.com/forum/ and your web store is www.mysite.com/store/. Where before you might have seen a url like mysite.com/store/addToCart.cfm?productid=5 now you see mysite.com/index.cfm?[event]=store.addToCart&productid=5. The /store/ directory simply became the "store circuit".
Getting back to the XML - currently Mach-II and Model-Glue both require XML to configure the application flow. Although Fusebox 4 had replaced the CFSWITCH with required XML, Fusebox 5 has added a notion of "implicit circuits" which allows the framework to operate without the use of XML. ColdBox and the onTap framework both use no XML for configuration. Yet in spite of the XML variance, all these frameworks ultimately handle application flow in very similar ways.
There is however one salient variation here with regard to the onTap framework. Of all five frameworks, the onTap framework is the only one that doesn't insist on all requests being funelled through the /index.cfm template. With regard to porting the Galleon forums this was a big help. Much of the time spent converting the Galleon to the other four frameworks involved changing all the links from urls like "messages.cfm" to "index.cfm?[event]=forum.messages". And this is due in part also to the fact that even though I used regular expressions to replace them in multiple files at once, every time I thought I was done I would find some place where the regular expression didn't work, because the URL was built as a variable like <cfset link = "messages.cfm?..." /> instead of being output directly in the link like <a href="messages.cfm?...">.
/index.cfm?[event]=store.addToCart
could just as easily be any of
I may be somewhat old-fashioned because the insistence on index.cfm is one of the things I've disliked about the other frameworks dating all the way back to Fusebox 3. Personally I think a framework should take advantage of the fact that there can be files other than /index.cfm in that directory. So because the onTap framework does this, that means that unless you've already written your application to use the urls required by another framework, there's no need to rewrite them to adopt this framework. And even though I don't spend time on SES URLs, this means that you don't have to do any extra work either in ColdFusion or in mod_rewrite to get SES URLs with the onTap framework, just create your directory structures.
However even though the onTap framework doesn't require the use of an event variable in the URL parameters, it does allow them, so you're actually free to either use index.cfm?[event]=event or just event.cfm - it's entirely up to you and how you want your links structured.
In addition to having different jargon, each framework defines the "circuit" or sub-application in a slightly different way. ColdBox and Model-Glue use CFCs, Mach-II uses XML, onTap uses directories (or "file folders"), and the latest version of Fusebox actually supports all of the above (it's up to you to decide which you like).
ColdBox: Event-handlers for a ColdBox application are conveniently located in the /handlers/ directory of the application skeleton. It isn't necessary to configure them in the ColdBox XML because the framework locates them by convention. So for example, code to execute the event "forum.index" can be found in the "index" method in the component /handlers/forum.cfc.
A "hello world" event-handler component for the url index.cfm?[event]=world.hello looks like this:
<cfcomponent displayname="world" extends="coldbox.system.eventhandler">
<cffunction name="hello" access="public" returntype="void" output="false">
<cfargument name="Event" type="coldbox.system.beans.requestContext" />
<cfset var rc = Event.getCollection() />
<cfset Event.setView("hello_world") />
</cffunction>
</cfcomponent>
Fusebox (traditional): Circuits for a traditional fusebox application can reside in any directory and are declared in the fusebox.xml configuration file. Each circuit has a circuit.xml file which defines the fuseactions within the circuit and the general behavior of the circuit.
The configuration for a Fusebox traditional "hello world" circuit to match the URL index.cfm?[event]=world.hello looks like this (highlights to show how these files are related):
<!DOCTYPE fusebox> <fusebox> <circuits> <circuit alias="world" path="controller/world/" parent="" /> <circuit alias="display" path="view/display/" parent="" /> </circuits> </fusebox>
<!DOCTYPE circuit>
<circuit access="public">
<fuseaction name="hello">
<do action="display.hello_world" />
</fuseaction>
</circuit>
<!DOCTYPE circuit> <circuit access="private"> <fuseaction name="hello_world"> <include template="hello_world" /> </fuseaction> </circuit>
Fusebox (implicit): You might notice in the code sample above that there's some repetition. For example, "hello_world" shows up in the <do> tag in the world circuit's "hello" fuseaction and is then repeated in the name of the fuseaction in the "display" circuit as well as the name of the include template, which is the only code declared in the dsp.hello_world fuseaction. With older versions of fusebox this was pretty common (it certainly was one of the things that frustrated me personally about earlier versions of Fusebox) and so partly as a result, Fusebox 5 now includes the ability to declare "implicit circuits". While this may sound like obscure, spooky language, it really isn't. An "implicit circuit" just means that Fusebox will do something very similar to what the ColdBox example above does.
So to convert the previous hello world example to implicit fusebox, you would delete all the above xml templates and replace them with the following CFC:
<cfcomponent displayname="world" output="false">
<cffunction name="hello" access="public">
<cfargument name="myFusebox" />
<cfargument name="event" />
<cfset myFusebox.do("display.hello_world") />
</cffunction>
</cfcomponent>
What's happening here is that Fusebox is detecting the /controller/world.cfc component and using that as the circuit for the "world" circuit. Then when the "hello" fuseaction executes the "display.hello_world" fuseaction the framework looks first for a /view/display.cfc and when it doesn't find one it then detects the /view/display/ and uses that as the circuit, mapping the file name of each file in the directory to a fuseaction of the same name.
Mach-II: This framework doesn't require you to group your events into "modules", allowing events to reside in the root of the application, accessed with urls like "index.cfm?[event]=hello" (note how the event lacks the "world." string indicating the "circuit"). In fact, modules didn't even exist in Mach-II until the most recent 1.5 release, which meant that no matter how large your application became, all your events had to be declared in the single mach-ii.xml config file. This is actually described in the Mach-II wiki as the reason for adding the modules feature to Mach-II. These modules closely resemble the traditioanl Fusebox approach where the module must be declared in the mach-ii.xml cconfiguration file and the module itself then has its own xml configuration.
A "hello world" module for the url index.cfm?[event]=world.hello looks like this (/mii is a mapping to the application root - I'm not certain if it's required):
<!DOCTYPE mach-ii PUBLIC "-//Mach-II//DTD Mach-II Configuration 1.5.0//EN" "http://www.mach-ii.com/dtds/mach-ii_1_5_0.dtd" > <mach-ii version="1.5"> <modules> <module name="world" file="/mii/config/worldmodule.xml" /> </modules> <properties> <property name="moduleDelimiter" value="." /> </properties> </mach-ii>
<!DOCTYPE mach-ii PUBLIC "-//Mach-II//DTD Mach-II Configuration 1.5.0//EN" "http://www.mach-ii.com/dtds/mach-ii_1_5_0.dtd" > <mach-ii version="1.5"> <event-handlers> <event-handler event=""> <view-page name="hello_world" /> </event-handler> </event-handlers> <page-views> <page-view name="hello_world" page="mii/views/hello_world.cfm" /> </page-views> </mach-ii>
Model-Glue: This framework turns the notion of the "circuit" a bit on its side, though it shouldn't be a horrible curve ball. In this framework, like Mach-II, an "event-handler" is an XML declaration for a single event and the "circuit" is a CFC called a "controller" that listens for messages called "broadcasts" declared in the event. This actually separates the controller ("circuit" or "sub-application") from the event, so that at least in theory, the event never actually contains any information about the controller (sub-application). Instead the event simply says "I need a list of forum threads - I don't know who's in charge of that, but somebody give it to me" - the framework then gathers its collection of controllers, weeds out the ones that have previously declared that they can provide that information and requests it from each controller. Like earlier versions of Mach-II however, because the event-handlers are all declared in the root ModelGlue.xml configuration file, event names don't reference the controllers in any way and instead of index.cfm?[event]=world.hello, URLs typically look like this: index.cfm?[event]=hello_world.
A "hello world" example for the url index.cfm?[event]=hello_world might look like this (the controller here is actually sort of gratuitous I just added it so you could get an idea of how "broadcasts" work):
<modelglue> <controllers> <controller name="world" type="glu.controller.world"> <message-listener message="needHelloWorldText" function="getHelloWorldText" /> </controller> </controllers> <event-handlers> <event-handler name="hello_world"> <broadcasts> <message name="needHelloWorldText" /> </broadcasts> <results /> <views> <include template="hello_world.cfm" /> </views> </event-handler> </event-handlers> </modelglue>
<cfcomponent displayname="world" extends="ModelGlue.unity.controller.Controller" output="false">
<cffunction name="getHelloWorldText" access="public" returntype="void" output="false">
<cfargument name="event" type="any">
<cfset event.setValue("HelloWorldText","Hello World") />
</cffunction>
</cfcomponent>
onTap: One of the original design concepts behind the onTap framework was that it wouldn't change the way you are probably already structuring your applications. For example, if you have an application with a store and everything related to the store is in a /store/ subdirectory then you'll still have that same /store/ subdirectory after converting your application to the onTap framework. Each directory (and file!) in the root of your application, is a sub-application and can have its own further sub-applications within it. None of your links need to change, although you may find them easier to manage. The trick to this is that directories and files in the root of your application are mapped directly to sub-application directories within the framework's /_components/ directory in a way similar to that which ColdBox automatically discovers its event handlers.
A "hello world" example in the onTap framework might look like this:
<cfsavecontent variable="tap.view.content"> <cfinclude template="/inc/hello_world.cfm" /> </cfsavecontent>
You also have a choice here, because there are several ways that the URL for this hello world example could be built. Any of these URLs would all execute the same code (above) and produce the same result page:
/main/world/hello.cfm /main/world.cfm?[event]=hello /main.cfm?[event]=world.hello
With previous versions of the framework you also had to create the base template, so for example, if your URL was /main.cfm?[event]=world/hello (the / and . are interchangeable) you would have to create this template in the root directory (which is now optional).
<!-- bootstrap the framework --> <cfinclude template="#request.tapi.process()#" />
And just to be totally biased here, I'd like to point out that with the exception of using a directory as an implicit fusebox circuit, the onTap example here requires only half as much code as even the smallest other example (ColdBox). If you were to use a directory as an implicit circuit for your fusebox controller, you could get away with the same three lines (or even fewer), although you would lose some of the advantages of an XML or CFC-based circuit, notably the pre and post-fuseaction features that are commonly used for creating layouts.
Ultimately I can (and will) work with any of these frameworks -- they're all fine as frameworks go. At the end of the day however because they essentially all do the same things, the framework that requires the least typing also tends to be the easiest to maintain and is usually the one I choose. That being the case, if I had to work with something other than my own framework, ColdBox and Fusebox 5 would be my first choices personally. Of course, your mileage may vary.*
* The verbosity and repetitiveness of the code involved in these simple "hello world" examples for other frameworks (Mach-II, Model-Glue and traditional Fusebox) really turned me off of those as options a few years ago when Fusebox 4 and earlier versions of Mach-II were brand new. It's interesting to me now looking back on it a few years later to see both Mach-II and Fusebox making significant feature changes to reduce the verbosity of their code, particularly because my complaints about verbosity had been met with a general response of "it's not a problem, go away if you don't like it". Apparently I wasn't theonly one frustrated by it, it just took a while for everyone else to catch on to how much extra maintenance work they were doing. I had done something similar to this Galleon project with a small blog application I'd written myself about that time. It wasn't as well received as I hope this will be, although one of the things I was doing then was a performance analysis. It seemed relevant. ;) I was timing the execution of three frameworks, onTap, Fusebox and Mach-II and someone else had hooked my code up to the Microsoft Web Stress analyzer and published the results. What I'd discovered at the time is that Fusebox was the most scalable in general terms, and that although the onTap framework was slower with smaller numbers of users, it scaled up better than Mach-II. In other words, where Mach-II started out marginally slow and seemed to scale up in an almost linear fashion, slowing down gradually as users were added, the onTap framework started out slower, but adding traffic (even reasonably large amounts of it) didn't bog it down at all. Whether that's still the case I have no idea - I would guess that Fusebox has become a little slower, particularly in implicit mode, but that's just a guess and is also equally likely to be true of any of the other frameworks. But I really think performance should NOT be the first concern when choosing a framework.
You've probably noticed something called an "Event" in the previous examples which is an argument passed to the methods of the event handlers. This is a common convention amongst MVC frameworks. The event object is a way of encouraging the developer (you) to stop using FORM and URL scope variables. "But I LIKE form and URL variables!" you say. Tough. Shut up and eat your vegetables. No really, you still have form and url variables, they're just in this new Event object, and they're not separated anymore, now they're all part of the same bunch of variables. This is an improvement for a few reasons, not the least of which being that now your form and url variables are interchangeable -- you can have a form that submits a form-field with the name "projectid" or you can put &formid=x in a link and both of those things have exactly the same effect. Secondly it also means your events and/or views can if necessary be executed with variables that didn't come from the form or URL scopes, but from some other as yet unknown source, such as a webservice or a scheduled task.
In previous versions of Fusebox (4 and prior), there wasn't (as far as I know) an event object. What those earlier versions of Fusebox had was an "attributes" structure. If you're familiar with custom tags, you should recognize that name right away. The framework simply took the form and URL scopes and mashed them together into a structure called "attributes". This worked fantastically well for the simple reason that it allowed all of the framework's "fuseactions" (events) to duck-type the input data by always behaving as though the fuseaction (event) were being called as a custom tag. This is also the way the onTap framework still handles event variables as an "attributes scope" instead of a "request collection" (spooky jargon that means the same thing), and I don't see it changing in the near future simply because I see no advantage to changing it.
In fact not only do I not see an advantage to changing it, the use of CFCs as event-handlers in ColdBox or circuits in Fusebox actually creates a new limitation that the attributes scope in the onTap events doesn't have. Namely these CFCs introduce the opportunity to have race conditions simply because you forgot to use the var keyword to scope a variable (which everyone does from time to time, even the inimitable ColdFusion Jedi Ray Camden* who created Galleon).
* You might notice I also slid in a convenient way to mention the VarScoper tool mentioned in Ray's blog as a must-have for writing circuits/handlers as CFCs. Keep reading to find out why. ;)
The reason why leaving the var scoping out creates a race condition is because the circuits or event-handlers are typically cached in memory once the application is in production. This actually makes the problem even worse because we usually turn off the caching on our development servers and have the framework (all of them) recreate the circuits or event-handlers on each request. So if we had such a race condition, we wouldn't know about it until much later once the code had gone into production and we started having weird problems that we couldn't explain because they didn't occur on the development server (not cached). It would only be after many, many hours of brutal hair-pulling and the gnashing of teeth that someone would lose an eye and we'd suddenly discover that we had a race condition because one of our method variables in our handler CFC wasn't var'd and it went into the variables scope of the CFC instead of staying neatly encapsulated in the method where it belonged.
In the onTap framework by comparison, it's actually considered preferable in some cases (or at least I consider it preferable) to put some information into the local "variables" scope BECAUSE being inside of a custom tag instead of inside a CFC method, the variables scope is self-contained within the tag without the need for any use of the var keyword (actually, you get an error if you try to use the var keyword). Thus the variables scope actually represents a safe place to put non var'd variables that we don't want in the attributes scope or "request collection". Putting it in the "request collection" is of course better than having an unvar'd variable in a CFC method, and while I do that for the sake of convenience when I'm working with ColdBox, I don't care for it. I'd much rather have an object reference I created internally sitting in the variables scope, neatly separated from the attributes scope that contains all the user-provided input. As a matter of fact I generally will avoid putting anything directly into the attributes scope except when I'm parameterizing a form or URL variable that I expect users to provide. If it's not something a user might have given me, imo it doesn't belong in their collection of data. So my attributes scope is typically treated almost like a read-only structure. And for these reasons I personally consider the custom tag used to bootstrap onTap framework events better encapsulated than the eventhandler CFCs in ColdBox or CFC-based circuits in Fusebox.
That being said, the Event object in these frameworks behave in nearly identical ways. Each framework's event object usually has these methods:
event.getValue("variable") - returns a value from the "request collection" by name
event.setValue("variable",value) - sets a value in the "request collection"
event.valueExists("variable") - tells you if the event has it,
because otherwise the getValue() method throws an error
event.getAllValues() - returns the structure/collection within the event object
this is more convenient much of the time when you need to param a lot of
variables that may or may not have been in the URL
Though in spite of the fact that the event object behaves nearly identically in all these frameworks, the names of the methods vary:
| Method | ColdBox | Fusebox | Mach-II | Model-Glue | onTap |
|---|---|---|---|---|---|
| getValue | getValue | getValue | getArg | getValue | N/A |
| setValue | setValue | setValue | setArg | setValue | |
| valueExists | valueExists | valueExists | isArgDefined | valueExists | |
| getAllValues | getCollection | getAllValues | getArgs | getAllValues |
Imo this just adds confusion without really adding any value, especially when you consider that ModelGlue goes a step further and instead of giving you an "event" object within the views gives you an attributes.viewState object with a different set of methods (attributes.viewState.getAll() insteaed of event.getAllValues()), but there you have it.* So to save myself the headache of loads and loads of tedious variable replacing in Ray's view templates, I just used getAllValues, getCollection or getArgs and set a local variable of "rc" (which is common in the MVC framework world as an abbreviation for "request collection"). Then I replaced all Ray's form and URL scoped variables with "rc" and it was off to the races! In the onTap framework I just used attributes instead of the "rc" structure and it's the same story -- just using the structure saved me huge headaches. My advice to anyone using a framework is to use as few of these methods in your views as possible - it may not be very likely that you'll switch to another framework, but being more loosely coupled is always a good thing(tm).
Lastly having said all that, if you're interested in the onTap framework and you have a lot of views to port from one of these other frameworks using these event methods, then this looks like a job for EventDuck! Just extract the archive directly into your onTap framework root directory and let the duck go to work, killing tough stuck-on coupling on-contact and quickly eliminating many of your conversion headaches.
* I've read that "viewState" is going to be replaced with the more conventional "event" object in the next release code named Gesture (this one was code named Unity).
As a part of the controller, each of these frameworks also provides a method of redirecting the browser to a new page. In non-framework applications like the original Galleon application, this is typically done with the CFLOCATION tag. The various frameworks provide alternatives to CFLOCATION for a couple for a couple of reasons. One of the reasons is because as the development team for a particular framework wanted to allow redirection to be specified in the XML configuration file(s) for that framework. ColdFusion tags won't execute from within an XML file, so they needed to find some way to give you access to execute the CFLOCATION tag. One of the other reasons is to allow the framework to build the URL for you in a consistent way specified in your configuration (for example SES URLs in Mach-II). Lastly a couple of these frameworks offer redirection tools as a means of providing functionality that doesn't exist natively in ColdFusion, for example, Fusebox uses it as a facade to allow the browser redirection to be switched between HTTP headers and Javascript, while Mach-II offers a feature enhancement to its redirection to allow you to send complex data (more complex than URL parameters) to the following page.
Here are some samples of the way redirection is performed in the frameworks I chose for this project.
Yet again, although this may sound like some tragically complicated and hopelessly obscure tech concept, it's really not. Sometimes you might come across a situation where you already have a particular piece of code (we'll call it a widget), and then you want that widget to appear within some other piece of code. A perhaps classic example of this would be a dashboard where you have a couple of different forms and a couple of different reports and you want to bring them all together in one place. You don't necessarily want to move them from where they already are in your application, but you'd like to have them additionally in this new dashboard. Now you could copy and paste that code into the dashboard, but if you're already written it once, pasting code is a BAD IDEA(tm). And that's much of the point behind creating these controller frameworks, encouraging the reuse of existing code.
Here are some samples of code for nested and chained events in the frameworks I chose.
Amongst the features of ColdFusion's Application.cfc are its own "event-handlers" - methods for executing code when certain specific application events occur. These include when an application is first loaded into memory (onApplicationStart), when it expires and is removed from memory (onApplicationEnd), when a user session starts or ends (onSessionStart and onSessionEnd) and at the beginning and end of a page request.
With any of these frameworks it's possible to execute code during any of these events by editing the method in the Application.cfc just as you would for an application with no framework. A little care should be taken however not to disturb the framework when doing this. Model-Glue doesn't seem to use any of these methods, so you're free to create your own safely. With ColdBox and Mach-II, the ApplicationSkeleton already has all these methods defined and you're safe to just add the code as long as you don't remove the code that's there already.
With Fusebox none of these methods are defined in the application skeleton, which contains a comment urging you to include super.onXXX() for each method. Example:
<cffunction name="onApplicationStart" access="public" output="false"> <cfset super.onApplicationStart()> ... my code ... </cffunction>
In addition, the Fusebox Application.cfc adds an onFuseboxApplicationStart() method which allows you to define code to execute when the fusebox framework is restarted. This is important because although the ColdFusion server only starts the application if it's not already in memory (if it timed out), the Fusebox framework is often restarted on each page request when the framework is configured in development mode (when you're working on it). So while you're working on the application, using onFuseboxApplicationStart() allows you to make framework configurations when they occur, which may be on each request or it may be only when the application starts.
ColdBox, Model-Glue and the onTap framework all provide some optional features for these events. In Model-Glue the framework broacasts events named OnRequestStart and OnRequestEnd so that any number of your controller CFCs can execute code during these events. Any or all controllers could listen for these events and all of them will execute on each request. Example:
<modelglue> <controllers> <controller name="forum" type="glu.controller.forum"> <message-listener message="OnRequestStart" function="OnRequestStart" /> </controller> <controller name="forumadmin" type="glu.controller.forumadmin"> <message-listener message="OnRequestStart" function="OnRequestStart" /> </controller> </controllers> </modelglue>
ColdBox allows you to declare a single event-handler method for each ColdFusion event in the XML config file. Only code in the one method will execute for each event. Example:
<Config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://www.coldboxframework.com/schema/config_2.5.0.xsd"> <Settings> ... <Setting name="RequestStartHandler" value="ehForums.onRequestStart"/> <Setting name="RequestEndHandler" value=""/> <Setting name="ApplicationStartHandler" value="ehForums.onAppStart" /> ... </Settings> </Config>
Of the five frameworks, the onTap framework is the only one that provides implicit request-event functionality. One of the fundamental design philosophies of the framework is that it's a cardinal sin to edit someone else's code. To that end the framework includes a wealth of features designed to allow you to make your own modifications without editing any of the existing templates. Code that executes for the Application.cfc event-handlers is found in a collection of predefined directories:
/_components/_appstart/ (onApplicationStart) /_components/_append/ (onApplicationEnd) /_components/_sessionstart/ (onSessionStart) /_components/_sessionend/ (onSessionEnd) /_components/_application/ (onRequestStart) * /_components/_onrequestend/ (onRequestEnd)
* The reason the onRequestStart directory is named /_application/ is because this framework was originally written on ColdFusion 5 before the Application.cfc was introduced and at the time, the onRequestStart feature was provided by a template called Application.cfm. As a result the framework retains that as the name of the event directory.
You can place as many templates as you want in any of these directories and all of them will execute during the event. This is a very useful method of integrating multiple sub-applications since the code for each sub-application will have its own template separate from any of the others.
The /_application/ and /_onrequestend/ directories provide an additional feature not found in the other frameworks, which is tiered, conditional execution. In the original Galleon application, Ray had actually already structured his code this way, the framework is just making it easier (and better). Ray had created the forum in the root and then a separate application in the /admin/ directory, but because he wanted the admin to share the same application name (and scope) and the same business objects (CFCs), he used a common trick that dates back to the early days of ColdFusion. Specifically, he nested his Application.cfm templates. If you look in the original Galleon application I included, you'll find this template:
<cfset variables.isAdmin = true>
<cfinclude template="../Application.cfm">
<cfif not request.udf.isLoggedOn()>
<cfinclude template="login.cfm">
<cfabort>
</cfif>
<!--- must be the correct authentication --->
<cfif not isUserInRole("forumsadmin")>
<cflocation url="../" addtoken="false">
<cfabort>
</cfif>
All this is doing is saying "I'm in the admin area, load the application (by including the Application.cfm from the parent directory) and perform some extra security." So at the beginning of any page request in the admin directory, two templates execute in roughly this order:
/Application.cfm /admin/Application.cfm
The onTap framework is just taking this same idea a little further and at the beginning of each page request in the admin directory, it will execute all templates in these directories in order:
/_components/_application/ /_components/admin/_application/ /_components/admin/[event]/_application/
Then at the end of the request, the order is reversed for any "cleanup" operations your application may need to perform:
/_components/admin/[event]/_onrequestend/ /_components/admin/_onrequestend/ /_components/_onrequestend/
So while code placed in /_components/_application/ will execute on every request, code placed in /_components/admin/_application will only execute if the user requests a page in the admin directory.
Something very similar to this (though in my biased opinion not quite as flexible) can also be accomplished with the pre-event and post-event features of both ColdBox and Fusebox using XML or CFC inheritance.
Both Fusebox and ColdBox include the notion of a pre/post event routine within a given circuit or event-handler. In ColdBox or implicit fusebox, this takes the form of a method with a given predefined name of "prehandler/posthandler" or "prefuseaction/postfuseaction". So the CFCs might look like this:
<cfcomponent extends="coldbox.system.eventhandler"> <cffunction name="prehandler" access="public" returntype="void" output="false"> <cfargument name="Event" type="coldbox.system.beans.requestContext" /> <cfset var rc = Event.getCollection() /> <cfparam name="rc.somevar" default="" /> </cffunction> </cfcomponent>
<cfcomponent> <cffunction name="prefuseaction" access="public"> <cfargument name="Event" /> <cfset var rc = Event.getCollection() /> <cfparam name="rc.somevar" default="" /> </cffunction> </cfcomponent>
In these cases if you want two event-handlers (or circuits) to execute the same code in the prehandler/prefuseaction, you can use the extends attribute of the cfcomponent tag to make them both extend another handler or circuit in the same directory and call super.prehandler() or super.prefuseaction() to allow them to share some code. Example:
<cfcomponent> <cffunction name="prefuseaction" extends="global" access="public"> <cfargument name="Event" /> <cfset var rc = Event.getCollection() /> <cfset super.prefuseaction(event) /> <cfparam name="rc.somevar" default="" /> </cffunction> </cfcomponent>
With traditional fusebox, the same functionality is provided in the circuit.xml file via <prefuseaction> and <postfuseaction> tags which behave much like the <fuseaction> tags, simply executing their code before or after the requested action. With these XML tags, the circuit can execute the prefuseaction or postfuseaction of its parent circuit by adding callsuper="true" to the tag.
In the onTap framework, the same tiered functionality is provided by the framework in its /_local/ directories the same way it provides tiered inclusion in the /_application/ directories described before. So for the url /store/addToCart.cfm, it will execute all code in these directories:
/_components/_local/ /_components/store/_local/ /_components/store/addtocart/_local/
Although the framework doesn't include a posthandler/postfuseaction equivalent by default, you could easily add one if you found a need. I haven't found a need, so I haven't added one. In Fusebox postfuseaction is typically used to create layouts, which are handled differently in the onTap framework, and I don't think postevent is used much in ColdBox.
It's possible that similar functionality might be achieved in Mach-II or Model-Glue although I believe either case would require the creation of a "plugin" to extend the framework.
As I said before, the business logic doesn't change from one framework to another. The manner in which business logic is accessed however does change a fair amount because that's part of the controller.
There are essentially two approaches to accessing your business objects within a framework. What separates these two approaches is the use of XML for defining "event-handlers" (or "fuseactions"). There is in essence an XML-based approach in which "listeners" and their notification within events (pages) must be specified in the XML, or there are CFML-based approaches in which there is no XML so the framework goes directly to some CFML code that can access the business objects as needed.
The XML approach while restrictive, encourages greater separation between your model components and your view. Adherents to the XML approach believe that you are less likely to mingle information about your view if your "listeners" are separate from your "event-handlers". In this approach, the "listener" is a CFC that is "just a messenger". It's only job is to know where the model components are and how to get information from them, but the listener itself is not a part of the model. It doesn't know anything about the rules of the business.
It wouldn't be entirely unfair to say that each circuit CFC in implicit Fusebox or "event-handler" CFC in ColdBox is analogous to both an entire XML config file from one of the XML frameworks and all of its listeners in one object.
In Galleon, Ray placed all his CFCs in the application scope, and his individual pages just referenced them directly. I've been somewhat lazy and in this release, I haven't bothered to separate his controller logic and view logic in either the Mach-II or Model-Glue samples. If you'd like to update them and send them back to me, your help would be appreciated. ;) But what I wanted to emphasize here for those unfamiliar with MVC fameworks is that in Mach-II and Model Glue, the only real function the listeners (or "controllers") serve is to know that those business objects are stored in the application scope and how to get data out of them.
ColdBox: This framework uses the CFML approach with each "event-handler" (analogous to a fusebox circuit) containing a method for each of its events where you can reference your business objects however you prever. It's up to you if you want to use an IoC framework or not, although the core framework distribution also includes an IoC plugin facade for providing a single interface to either ColdSpring or Lightwire, which is a nice touch in spite of the fact that I think it could have been designed more extensibly.
<cfcomponent displayname="store" extends="coldbox.system.eventhandler"> <cffunction name="browseByCategory" access="public" returntype="void" output="false"> <cfargument name="Event" type="coldbox.system.beans.requestContext" /> <cfset var rc = Event.getCollection() /> <cfparam name="rc.categoryid" type="string" default="" /> <cfset rc.qProduct = getPlugin("ioc").getBean("storeGateway").getProductsByCategory(rc.categoryid) /> <cfset Event.setView("hello_world") /> </cffunction> </cfcomponent>
ADVANCED: I recomment actually creating a separate function to return just the store gateway, i.e. rc.qProduct = getGateway("store").getProductsByCategory(rc.productid) instead of chaining them all out like this. This method adheres to the underrated best practice of the "law of demeter" to reduce coupling in your aplication and is similar in nature to the broadcast-style listener notification in Model-Glue in that it prevents the method for the current event from knowing about the fact that the storeGateway is kept in the IOC plugin. This is also the purpose behind listeners in Mach-II and Model-Glue.
Fusebox (implicit): If you choose to use CFC-based event-handlers for your controller circuit(s) in a Fusebox application, you have the option of developing them like ColdBox or you can call nested fuseactions in the model layer to get queries and other data from your model like traditional Fusebox (see nested events).
Fusebox (traditional): In this framework you have the choice of using <include> tags in the XML to simply include CFM files that either reference your business objects or just fetch queries directly from the database (that would be traditional, "procedural" coding style), or you can use either the <set> tag or the <invoke> tag to tell fusebox that you want information directly from a business object.
Typically regardless of which of these approaches is chosen, at least in recent years, the controller circuit won't do either of these things directly so there's usually a <do> in the controller circuit that tells the framework to execute a fuseaction in a model circuit which performs the actual work of determining where the data will come from and how. (Yet again, this is for the same reason that Mach-II and Model-Glue have listeners/controllers.)
The following SET and INVOKE examples produce the same results:
<fuseaction name="browseByCategory"> <do action="storeModel.browseByCategory" /> <do action="storeView.browseByCategory" /> </fuseaction>
<fuseaction name="browseByCategory">
<set name="request.qProduct" value="#application.store.getProductsByCategory(productid)#" />
<invoke object="application.store" method="getProductsByCategory" returnvariable="qProduct">
<argument value="#productid#" />
</invoke>
</fuseaction>
ADVANCED: Fusebox 5 also includes an <instantiate> tag which if used properly in conjunction with nested fuseactions can be used to simulate IoC features. For example, any fuseaction in the model which needs the storeGateway would first <do fuseaction="broadcast.needStoreGateway"> which would in turn check to see <if condition="isDefined('application.storeGateway')"> and if <false> would then <instantiate> the necessary object and place it in the variable application.storeGateway. This would mean that the storegateway is only created in one place within the application conforming to the best practice of Don't Repeat Yourself (DRY). Although often an IoC framework is used, which does much the same thing usually in a more sophisticated way with extra options like autowiring (which just means automation).
Mach-II: This framework uses an XML approach in which the syntax for notifying listeners of events is very direct. The event knows which listeners to <notify> and what variables need to be returned from those notifications.
<listeners> <listener name="storeListener" type="mii.listeners.store" /> </listeners> <event-handlers> <event-handler event="browseStoreByCategory" access="public"> <notify listener="storeListener" method="getProductsByCategory" resultArg="qProduct" /> <view-page name="browseStore" /> </event-handler> </event-handlers> <page-views> <page-view name="browseStore" page="mii/views/browseStore.cfm"/> </page-views>
Model-Glue: This framework uses the XML approach to accessing business objects with one small change I've already mentioned. Model-Glue doesn't have any analogy to a circuit - at least not in the latest release. The best analogy I could come up with is the "controllers" which are the "listeners" in Model-Glue. What's different about the Model-Glue controller, compared to the Mach-II listener is that the controller isn't asked anything directly by the <event-handlers>. Instead the controller declares what it can do (which <broadcasts><message>s it understands), the event-handler declares what it needs in its <broadcasts> and the framework then ties the two together, so that individually the controller and the event-handler don't actually know anything about each other. The example for this is shown in the circuits / modules section.
There is one other caveat with Model-Glue and I suppose here is as good a place as any to put it. The use of an IoC framework like ColdSpring or Lightwire for managing your business objects in any of these frameworks is optional. However I discovered when I started installing the frameworks that, although I hadn't planned to install ColdSpring I didn't have a choice. This is because Model-Glue uses ColdSpring for managing the framework objects. So even if you don't use ColdSpring for your own business objects, you would still be required to install ColdSpring if you want to use this release of the Model-Glue framework.
onTap: I mentioned before how the onTap framework offers tiered inclusion of code, so for the url /store/addToCart.cfm, it will execute all code in these directories:
/_components/_local/ /_components/store/_local/ /_components/store/addtocart/_local/
These /_local/ directories are where you place your controller logic -- access IoC frameworks if necessary and fetch data from your business objects (your model). You can think of the /_local/ directories as being analogous to the contents of the <event-handler> tags in either Mach-II or model glue, the <fuseaction> tags in traditional fusebox or the event/fuseaction methods in a implicit Fusebox circuit CFC or a ColdBox event-handler CFC. This tiered inclusion of code is one of the fundamental principals of the framework, dating back to ColdFusion 5 and it's stayed in the framework rather than being replaced by CFCs because it's very simple and it works VERY well for a number of common purposes.
One of the fundamental philosophies of the framework is that it should be a cardinal sin to edit someone else's code. By this I don't mean coworkers code, I mean code provided to or by third parties. If for example you write an application to sell to other people and they want customizations, never under any circumstances should you ever edit as much as ONE CHARACTER of your own code to accomodate their customizations. Your code is your code and their code is their code and never the twain shall meet. (This should sound familiar if you read the explanation of MVC above.)
Client customization requests have been the number one biggest headache for nearly every company I've ever worked for. They were at Innovative Studios, Parts.com, Charter Schools and Site Manageware in Florida, they were at CardioConcepts in Virginia, they were at DealerPeak in Oregon, and they are at GetFused in Boston. And they're the one thing consistently not addressed (usually not even mentioned) by frameworks - except the onTap framework. The only places I've worked where client customization requests weren't a major ongoing headache negatively affecting the company's bottom line, was when I worked for government agencies on software that couldn't leave the company. There is a huge expense to software that's "easy to modify" that's like an elephant in the room that nobody wants to talk about. Companies just buldoze along, hapily hemorhaging money while they complain about how much time their client customization requests are costing them, rather than create a solution. Alan Cooper described it at length in his book the Inmates Are Running the Asylum, although he claimed there was no solution.
The onTap framework provides a solution through this very simple tiered architecture.
Now lets say hypothetically that you created a shopping cart application using the onTap framework and it has a "dashboard" page that shows a bunch of navigation and several tools in one place. This is a landing page where users come after logging in to your application. You also want other people to be able to create plugins for your application to add new features, which is a win-win situation since it both provides opportunities for others to make money with your product and it's value-added to your product.
There is actually a company called Able Commerce that used to be quite popular in the ColdFusion community, who created a shopping cart much like this. So how did they handle installation of plugins? Unzip the plugin into the shopping cart. Really? It's that simple? Yes - and any files that need to be modified by the plugin will be OVERWRITTEN during installation. WHAT?! Amazingly this is a product that was very popular and there are (or at least there were) several companies that made decent money just developing plugins for it, in spite of the fact that two plugins would often OVERWRITE THE SAME FILE. This is insanely sloppy and of course it creates all sorts of version dependencies etc. (See the added expense of software that's easy to modify.)
So lets play that scenario over again using the onTap framework. You create your application, you put the dashboard in /store/admin/index.cfm and all code in these directories are included:
/_components/_local/ /_components/store/_local/ /_components/store/admin/_local/ /_components/store/admin/index/_local/
With any of these other frameworks (the ones I included), a layman would not be able to install any third-party extensions for your wonderful web store. Installation of the plugin would require an understanding of programming and there would be a set of instructions about where to place components and display templates and which event-handlers would need to be modified. In the onTap framework there is a plugin manager project which provides a means of creating installers for third-party extensions to your web store (or other applications) much like the Windows add/remove programs wizard. This allows laymen to install these value-added third-party extensions via a browser, with no programming knowledge. The plugin simply copies its files into the /_local/ directories as needed, where they sit, totally separate from your own. So after the installation of a third-party plugin for your shopping cart, you might see this in the local directory:
/_components/store/admin/index/_local/100_shoptillyoudrop.cfm /_components/store/admin/index/_local/200_eatatjoesplugin.cfm
The first file, 100_shoptillyoudrop.cfm contains all the event-handler logic required by the original store application and then is followed up by all the event-handler logic required by the Eat At Joes plugin in the 200_eatatjoesplugin.cfm template. And this is essentially the same way you would provide client customizations without modifying any of your own application code. If you have multiple clients sharing the same application, the framework offers a separate set of directories in /_components/_brand/[nameofclient]/ which allows you to separate the customization code for each client and see by glancing at the directory how much customization work you've done for them and how their customizations differ from other clients. You might for example realize that several clients had asked for very similar customizations, an indication that perhaps other clients in the future may make similar requests and an opportunity to predict what changes are in-demand moving forward. By quickly scanning through the client customization directories, you can see how many have asked for the change and make decisions about incorporating the customization into the next release of your product.
Let me rein myself in a bit and give you another example of something a little simpler and a little more common to show how this tiered inclusion architecture saves you time and headaches. How about an analogy to Model-Glue's broadcasts? Lets say somebody is using this shopping cart you designed and they go to make changes to one of their products at the url /store/admin/product/detail.cfm?productid=5. In any of the other frameworks I've examined this would be something like /index.cfm?event=store.productAdmin&productid=5. You can imagine that this event is going to need to get product information from somewhere - and so are a number of other events like store.productEdit or store.productSave. In a Mach-II or Model-Glue application, the <event-handler> for the store.productAdmin event would be required to <notify> a "productListener" or <broadcast> a "needsProductInfo" message. Even in implicit Fusebox or ColdBox, each fuseaction method in the circuit CFC or event method in the handler CFC would have to separately invoke a method to get the product information. Not so in the onTap framework. With tiered inclusion, the product detail, edit and save events (and maybe even a few more) can all share two lines of code to fetch the same product info, instead of the half-dozen or so required by other frameworks. Don't believe me? Check out these examples.
The onTap framework's Tiered inclusion is interception, broadcast-style listener notification, pre/post event and parent-circuit functionality and the better part of "aspect programming" (AOP) all rolled into one AND it has fewer moving parts, it's less coupled and its learning curve is much smaller.
I've broken aspects of the view (what the user sees) down into three essential components, those being Layouts, Navigation and Content Variables. Layouts represent a place to put general navigation for an application and as such they surround the content and bind it together like the Force. Navigation of course is just what it sounds like, but if you recall from previous sections, the onTap framework is the only framework in my list that doesn't insist on forcing all page requests through index.cfm. Similarly most frameworks also include some method of managing navigation, although this is actually more of a help than a restriction. And lastly there are content variables, which also ought to be pretty self explanitory, although in most of these frameworks in order to accomodate the way that layouts are created the framework itself actually requires that certain content variables be created.
But let me back up a little bit, because the previous paragraph sounds kind of complicated to me and it's really not. When we're talking about the "view" all we're really talking about is a way to generate HTML (or sometimes PDF or RSS, etc. but usually HTML). And so ultimately all we're really talking about is a way for the framework to allow you to include some run-o-the-mill ColdFusion template (a .cfm, not a .cfc) that usually has at least one pair of CFOUTPUT tags in it so that the dynamic content from the database can find its way to the user's browser. The only reason why content variables are even needed in this context (instead of just plain "including" the view), is because many of these frameworks are structured in such a way that "just including" would be somewhat difficult or would introduce coupling that you don't want. Remember you want your "views" to be separate from the rest of your application so you can reuse them and in theory you can take them with you easily to any other framework or application.
So since all we're talking about really, in a nutshell, is a simple CFINCLUDE (at least with any of the frameworks I've described) you just need to know two things to set up your view. First you need to know where to place your .cfm templates and secondly you need to know the language of the framework's API (fancy word for tools) to tell the framework which view template you want to include and when.
ColdBox: The ColdBox framework requires all views to be placed in the directory /views/. (convenient huh?) You can of course create subdirectories for different related view templates and I personally consider this a best practice. For each event one view needs to be set in the eventhandler cfc in the /handlers/ directory. This is done with the Event.setView() method, which accepts as its argument the relative path to the desired view template, excluding the .cfm file extension. So in the example below, the call to event.setView() will cause the framework to use the template "/views/store/index.cfm".
<cffunction name="index" output="false" returntype="void">
<cfargument name="event" required="true" />
... get model data ...
<cfset Event.setView("store/index") />
</cffunction>
Fusebox (traditional): This framework allows files to be included within any circuit directory, although in recent years the popular convention is to only place view templates in circuits within the /view/ directory. The application skeleton I downloaded placed views templates specifically within /view/display/. In the circuit XML file, an <include> tag tells the framework to display a given template. So in the example below, the include tag will load the template "/view/display/store/index.cfm".
<fuseaction name="index">
... get model information ...
<do action="storeView.index" />
</fuseaction>
<fuseaction name="index"> <include template="index" /> </fuseaction>
Fusebox (implicit): Because the popular convention in Fusebox has become to separate all view activity into its own circuit and then call fuseactions in that circuit to generate output, this had created a lot of duplicated code in traditional, XML-driven fusebox applications where there were many view circuits with very few fuseactions containing anything other than the above three lines, which were then also repeated more or less in the controller circuit with its <do> tag to call the view. So all this repetitive coding (which had been my biggest complaint about fusebox for a long time) inspired the notion of using a directory as a circuit in Fusebox 5.5 (very similar to how the onTap framework began several years earlier). So in implicit Fusebox, in addition to being allowed to use CFCs as circuits, you can also just use the directory with no CFC and no XML and the framework will include a file with a name matching the requested fuseaction. (Although as nice as it is, with the addition of two new arguments in the CFC that weren't there in the circuit.xml, you only end up saving 1-line of code. Doh!)
<cffunction name="index">
<cfargument name="myFusebox" required="true" />
<cfargument name="event" required="true" />
... get model information ...
<cfset myFusebox.do(action="storeView.index") />
</cffunction>
Mach-II: Views in this framework must be declared in the mach-ii.xml configuration file in the page-views section. Each template you want to use must be declared and given a name and a path. In my experience the framework seemed to prepend a forward slash / to the template path of the view, meaning that all views are realtive from a mapping -- which defaults to the web root. However if you're not expecting the template paths to use mappings, you might be surprised if you try and create a view of /store/blah.cfm not realizing that your server already has a mapping of "/store" configured in some other location. So if you're using Mach-II and you find yourself scratching your head over the server not finding a file, check your mappings. Once they're configured, each event-handler declares the views it will use with a view-page tag.
<event-handlers> <event-handler event="storeIndex" access="public"> ... get model info ... <view-page name="storeIndex" /> </event-handler> </event-handlers> <page-views> <page-view name="storeIndex" page="mii/views/store/index.cfm" /> </page-views>
Model-Glue: Mach-II and Model-Glue oddly seemed to trade places with regard to abstraction. Mach-II requires you to declare all your views and give them names (more abstract) before you can use them in your event-handlers, but listeners are notified directly. Model-Glue requires you to define your event-listener notifications using broadcast syntax (more abstract), but views templates are assigned directly. In any case like ColdBox the paths to views are relative from the directory /views/ (convenient ain't it?) and the <include> tag is used to call them. So the example below will include the template
<event-handler name="storeIndex">
<broadcasts>
... get model info ...
</broadcasts>
<views>
<include template="store/index.cfm" />
</views>
</event-handler>
onTap: In my biased opinion, the onTap framework is the easiest to work with when it comes to views, not to mention it also provides some view-enhancement features the other frameworks don't.
The onTap framework doesn't require that you place your view templates anywhere in particular. It recommends that you place them somewhere within the /_components/_includes/ directory which has a convenient mapping of /inc/ so that you actually can just use a standard CFINCLUDE tag to get your views. And as a "best practice" I generally group my views into separate subdirectories for each sub-application I create.
<cfinclude template="/inc/store/index.cfm" />
If you're coming from other frameworks and you're not interested in the onTap framework's extra view features, you can stop there, that's all you need. In fact, the include isn't even necessary. If you had a view template that you know is only going to be used once in your application, you could just output your HTML right here in /index/_process.cfm. I'm using the CFINCLUDE in this example to show "best practice" for code reuse.
A content variable is typically just a string, containing information the user will see, usually although not always in HTML format. So a content variable is no different than any other variable except for the fact that its specific purpose is to be displayed to the user instead of performing other functions like math, pagination, etc.
In general, the reason these frameworks use content variables at all is for the purpose of wrapping layouts around them. For example the layout might have a title at the top that changes on each page, a sidebar on the left or right containing some dynamic navigation elements and a main content area. Each of these sections then needs to be set to a variable before the layout is displayed, otherwise ColdFusion will throw an error indicating that the variable is undefined. Once in a while, you'll find a case where a particular display expects several different content variables for different sections of the content, like in a dashboard, but those are the exception rather than the rule. Most of the time, the framework just wants to make sure that it has a string with the HTML for the "recent blogs" pod before it tries to display the pod in the layout. Though most layouts will only be concerned with one content variable -- the main body of the page.
Frameworks achieve the setting of content variables in different ways and some frameworks also require that your displays or layouts then reference those variables in specific ways.
ColdBox: This framework provides access to set content variables within the event-handlers via its "renderer" plugin.
<cffunction name="index" output="false" returntype="void">
<cfargument name="Event" type="coldbox.system.beans.requestContext" />
<cfset var rc = event.getCollection() />
<cfset rc.eatAtJoesPod = getPlugin('renderer').renderView('eatatjoes/pod') />
<cfset Event.setView("store/index") />
</cffunction>
Fusebox (traditional): This framework allows content variables to be set in two different places via the same "contentvariable" attribute in either the <include> tag or the <do> tag in a circuit XML file.
<fuseaction name="index"> <do action="storeModel.getStoreData" /> <include template="searchform" contentvariable="searchform" /> <do action="storeView.storeIndex" contentvariable="body" /> </fuseaction>
Fusebox (implicit): Setting content variables in implicit fusebox is pretty similar to ColdBox. I'm not sure if myFusebox has an equivalent of the <include> tag in XML-based fusebox, but if so, it would essentially be the same thing -- a contentvariable argument in the myFusebox.include() function.
<cffunction name="index">
<cfargument name="myFusebox" type="any" required="true" />
<cfargument name="Event" type="any" required="true" />
<cfset myFusebox.do(action="storeView.storefront",contentvariable="body")#" />
</cffunction>
Mach-II: This framework allows the content of its page-views to be saved to content variables via the contentArg attribute of its <view-page> tag. Because Mach-II only allows event-chaining and not nested events, there is no equivalent of the <do contentvariable="x"> syntax in Fusebox. Simple strings however can also be set via the <event-arg> tag.
<event-handlers>
<event-handler event="storefront" access="public">
<event-arg name="pageTitle" value="Eat At Joes!" />
<view-page name="storefront" contentArg="mainContent" />
</event-handler>
</event-handlers>
<page-views>
<page-view name="storefront" page="/mii/views/storefront.cfm" />
</page-views>
The framework then sets that variable as an argument in the event so in your layout or other display templates that need to use the content variable, when you want to display it, you have to use the event object to get it like this:
<div id="header">...</div> <div id="content"> <cfoutput>#event.getArg('mainContent')#</cfoutput> </div> or <cfset rc = event.getArgs() /> <div id="header">...</div> <div id="content"> <cfoutput>#rc.mainContent#</cfoutput> </div>
Model-Glue: As with Mach-II, this framework only allows chained events, not nested events, so there's no equivalent of the Fusebox method for fetching the result of a fuseaction as a content variable. Content variables are set via the include tag. The content variable is set using the name attribute.
<event-handler name="storeIndex">
<broadcasts>
... get model info ...
</broadcasts>
<views>
<include name="body" template="store/index.cfm" />
</views>
</event-handler>
Instead of setting these as values in the event, Model-Glue sets these in an object called the ViewCollection, so instead of fetching the variables from the event, you have to get them in the layout or other display template this way:
<div id="header">...</div>
<div id="content">
<cfoutput>#ViewCollection.getView('body')#</cfoutput>
</div>
onTap: For general purposes this will suffice:
<cfsavecontent variable="tap.view.content">
<cfinclude template="/inc/store/index.cfm" />
</cfsavecontent>
However, having a flat string doesn't offer a whole lot of flexibility with regard to client customizations as I mentioned before. If client customization is an issue, then you might want to look into the framework's CF_HTML tag for creating more modular display. The framework's html library has provided lots of bells and whistles for the display for a long time, like AJAX partial-page rendering, tabsets, trees, and form validation. Here's an example of a form that provides both client-side (JavaScript) and secure server-side validation (unlike CFFORM which is dangerous). Formatting for this form is provided via an XSL template, the same way ColdFusion's native XML-forms work.
<cf_html return="tap.view.content"> <div xmlns:tap="xml.tapogee.com"> <div>Contact Us</div> <tap:form action="?" tap:variable="tap.view.form"> <input type="hidden" name="[event]" value="send" /> <input type="text" name="subject" label="Subject" tap:required="true" tap:default="Request for Quote" /> <tap:formgroup label="From"> <input type="text" name="firstname" label="First Name" /> <input type="text" name="lastname" label="Last Name" /> <input type="text" name="email" label="Email" tap:validate="email" /> </tap:formgroup> <textarea name="message" label="Message" tap:required="true" /> </tap:form> </div> </cf_html>
You might have noticed that I didn't put any cfoutput tags in this example and that none of the input elements have value attributes. This is deliberate. The framework implies values for the form input elements from the attributes scope, so it works on much the same principal as for example ColdSpring's autowiring. If a particular form field needs to have a specific value like the hidden input in this example which has a value of "send", then adding the value attribute in the XML will overwrite the autowiring feature for that input element. This is just one of the many ways this library can save you a lot of repetitive coding.
Getting back to content variables however, you might have also noticed that there's an extra custom attribute called "tap:variable" in the form tag. That attribute tells the HTML library that you want a pointer to that particular HTML element because you probably want to do something else with it later. In this case, I want to validate the form. If you read the previous sections of the article, you should know that when this form is submitted, framework will append the [event] value "send" from the form to the URL of the page "/contactus.cfm" to produce the event "contactus/send". It will then proceed to execute all code in these directories:
/_components/_local/ /_components/contactus/_local/ /_components/contactus/send/_local
You might recall from the Business Logic section that the framework allows me to group related events together so that they can all share the same code to do something like fetching information about a product from the business objects. Here because I've created a sub-event (contactus/send) under the "contactus" event, I can reuse any variables I've created in the parent event. So within my send event the form is already built, all I have to do is validate it like this:
<cf_validate form="#tap.view.form#">
<!--- Woohoo! My form validated successfully! Send the Email --->
<cf_mail ...>...</cf_mail>
</cf_validate>
So what happens if the form doesn't validate? Simple - error messages are injected into the form display and the form is displayed again this time pre-filled with all the values the user supplied when they submitted the form. So this form will function nearly identically with JavaScript either enabled or disabled in the browser. Whatever differences may occur with JavaScript disabled are completely cosmetic.
Under the hood what makes the framework ideal for using content variables for things like this form validation example is the same thing that makes it ideal for cilent customizations. I used this form validation example because it's one of the most common tasks we perform in general as programmers. Client customization requests however are an important issue because of their significant negative impact on companies' finances and ability to work on continued development of their core products.
Here are some more examples of how client customizations can be accomodated quickly and easily using the onTap framework's HTML library.
So having said that content variables are mostly about creating layouts, lets see how the layouts work. Two of these frameworks, ColdBox and the onTap framework have built-in features for setting and displaying layouts. The remaining three have no built-in features for managing layouts, leaving you to make your own decisions about how to manage them. The the community of developers working with each framework has produced a common technique for managing them in each framework.
ColdBox: This framework makes managing layouts pretty easy. The configuration file /config/coldbox.xml.cfm contains a Layouts section where you can declare layouts. It's required that you have a DefaultLayout defined in this config file (the skeleton has one preset for you). The paths to these layouts are relative to the directory /layouts/. (convenient huh?)
<Layouts> <!--Declare the default layout, mandatory--> <DefaultLayout>Layout.Main.cfm</DefaultLayout> <!--Declare other layouts, with view assignments--> <Layout file="Layout.Open.cfm" name="Open"> <!--You can declare all the views that you want to appear with the above layout--> <Folder>store</Folder> <View>walla/walla/washington</View> </Layout> </Layouts>
NOTE: Ignore the "name" attribute in the layout tag! It doesn't do anything. It used to do something in earlier versions, but not anymore.
Once the default layout is set, that layout will be used for any page request that hasn't defined a different layout. There are three ways to set the layout for a specific view. Either you can declare it with the View tag you see here inside the Layout tag for the desired layout, you can use the Folder tag shown here to set it as the default layout for several individual view templates, or you can set the layout specifically within the event handler CFC.
The Folder tag is a little misleading. I initially thought, if I had a /store/ directory and then used "store" as the name of the folder, it would apply to any views inside the /store/ directory but that's not entirely accurate. It will actually apply to any view with the string "store" anywhere in the path, so it will include views in that directory, but it will also apply to the view "store.cfm" or to the view "storelocator" or to the view "wincellar/stored".
To set the view specifically within the event handler, use this:
<cffunction name="index" output="false" returntype="void">
<cfargument name="Event" type="coldbox.system.beans.requestContext" />
<cfset var rc = event.getCollection() />
<cfset Event.setLayout("Layout.Store") />
<cfset Event.setView("store/index") />
</cffunction>
The argument to the Event.setLayout() method is the path to the desired layout file, excluding the .cfm file extension. If the layout isn't explicitly set in the event handler CFC (above) then the framework will select one of the default layouts defined in the XML config file in the previous example.
The layout itself requires a specific structure, although it's not very complicated. To display the body of the page use the method renderView() and to display other views (think of these as "includes") you can either use cfinclude in the layout template, or you can use renderView() again and pass in the path to the other view you would like to include.
<cfset rc = event.getCollection() />
<cfouptut>
<html>
<head>
#renderView("htmlhead")#
</head>
<body>
<div id="header">#renderView("store/header")#</div>
<div id="content">#renderView()#</div>
<div id="footer">#renderView("store/footer")#</div>
</body>
</html>
</cfoutput>
Fusebox (traditional): This framework has no built-in layout features, so it's up to you to determine how you want to manage them. That being said, the common practice in the Fusebox community is to call another fuseaction in a "layout" circuit in the postfuseaction of the controller circuit. Don't worry, it's not nearly as complicated as it sounds. It's actually very simple:
<postfuseaction> <do action="layout.store" /> </postfuseaction>
<fuseaction name="store"> <include template="lay_store" /> </fuseaction>
<cfouptut> <html> <head> #htmlhead# </head> <body> <div id="header">#header#</div> <div id="content">#body#</div> <div id="footer">#footer#</div> </body> </html> </cfoutput>
Fusebox (implicit): Same as traditional fusebox except for the change of syntax of course from circuit.xml to circuit.cfc. The syntax for implicit postfuseaction can be seen above. And if you use the directory as a circuit for the layout circuit, then you don't need any configuration there, just use the path of the layout you want in myFusebox.do("layout.[mylayout]").
Mach-II: In order to get Ray's layout to work in Mach-II I downloaded a LayoutManager plugin provided by Martin Laine. (Thanks Martin!) This configuration is a little more involved than the configuration for the other frameworks, but still not too bad, even considering the fact that I had to make a small change to Martin's plugin to account for the fact that it relied on a deprecated attribute from earlier versions of Mach-II. The plugin itself /layoutManager.cfc is placed in the /plugins/ directory. Then in the config file for each module where I want to use it, I have to install/configure the plugin for the module and then I can create the layout for that module. Within the layout event I append all the content I want on the page to the variable "request.content.final" which the LayoutManager then displays after the framework finishes processing its queue of events. Here is the code from the module I created for Galleon.
<event-handlers> <event-handler event="layout"> <view-page name="header" contentArg="finalContent" append="true" /> <view-page name="content" contentArg="finalContent" append="true" /> <view-page name="footer" contentArg="finalContent" append="true" /> </event-handler> </event-handlers> <page-views> <page-view name="content" page="mii/views/content.cfm" /> <page-view name="header" page="mii/views/galleon/pagetemplates/main_header.cfm" /> <page-view name="footer" page="mii/views/galleon/pagetemplates/main_footer.cfm" /> ... </page-views> <plugins> <plugin name="layoutManager" type="mii.plugins.layoutManager"> <parameters> <parameter name="defaultLayoutEvent" value="layout" /> <parameter name="finalOutputKey" value="finalContent" /> </parameters> </plugin> </plugins>
Model-Glue: I didn't find any information about "plugins" for Model-Glue. If I had, I might have used an approach like the one in Mach-II. Instead I created a Layout event and then announced that event in the results of all the other events in the config file. To me this feels rather redundant, but I digress...
<event-handler name="forumadmin.stats"> <broadcasts /> <results> <result do="layout.forumadmin" /> </results> <views> <include name="body" template="galleon/admin/stats.cfm" /> </views> </event-handler> <event-handler name="layout.forumAdmin"> <broadcasts /> <results /> <views> <include name="template" template="galleon/admin/layout.cfm" /> </views> </event-handler>
The <result> tag in the forumadmin.stats event then announces another chained event that occurs after the current event. The <include> tag in the views section of the forumadmin.stats event sets the content of the /galleon/admin/stats.cfm template to the view "body". I say "view" and not "variable" because if you remember from the content variables section, Model-Glue places these in a ViewCollection object. And as a result, the final layout template looks like this:
<cfinclude template="pagetemplates/main_header.cfm">
<cfoutput>#viewcollection.getView("body")#</cfoutput>
<cfinclude template="pagetemplates/main_footer.cfm">
onTap: My own framework has a fairly simple implicit approach to layouts. Like the /_application/ and /_local/ directories, layouts are nested. So for example, when you visit the url /store/index.cfm the framework will look for these layout templates:
/_components/_layout.cfm /_components/store/_layout.cfm /_components/store/index/_layout.cfm
Each layout is then included before the content is displayed, and then they're included again in reverse order after the content is displayed. Within the layout itself the variable "tap.layout" is used to determine what is displayed. So this sample code should look familiar to a lot of ColdFusion custom-tag authors:
<cfswitch expression="#tap.layout#"> <cfcase value="header"> <div class="header"> ... do headerish things ... </div> </cfcase> <cfcase value="footer"> <div class="footer"> ... do footerish things ... </div> </cfcase> </cfswitch>
The upshot of tiered inclusion like this is that its a lot easier to create nested layouts where you've got a general container with look-and-feel for your entire site and then within the store you can easily add a store menu and within a category for the story you can easily add a category menu, etc.
There's one other thing to consider about this layout however and that is that there aren't any <html>, <body> or <head> tags in it. These layouts really are, strictly layouts, rather than being inclusive of the meta-structure of the HTML page.
Other frameworks tend to be somewhat "purist" about the notion of "layouts" or for that matter "views" in the sense that they adhere to the popular notion amongst programmers that the same "layout" mechanism should be able to produce content in any given format. So for example although well over 90% of your content is liable to be delivered as HTML, these frameworks insist that the layout include all of the document meta-structure and formatting, so that you can deliver RSS, XML, PDF or Flash by switching to another format. I personally don't consider those edge-cases common enough to avoid creating helpers for the most common tasks, so while the onTap framework certainly doesn't prevent you from delivering any of that other content, it assumes that 90% or more of your content will be delivered as HTML and provides some syntax sugar to help with that task.
Prior to rendering any layouts, the framework will first render the html doctype, head and body tags. There are a number of variables for managing these which I won't get into right now, just know that they're all totally configurable. Like the tiered inclusion in the /_local/ directories, content in the head section of the html document is tiered and included automatically. So for example if you visit the page /store/index.cfm then the framework will automatically include all code in these directories.
/_components/_htmlhead/ /_components/store/_htmlhead/ /_components/store/index/_htmlhead/
Code in these directories can be in three formats, being CFML with a .cfm extension, JavaScript with a .js extension or Cascading Style Sheets (CSS) with a .css extension. Since these directories are tiered and included, you'll never have to specify the location of your "global style sheet" for your application, you'll never have to write a link tag for it or worry about its path, because you can just place the CSS code in /_components/_htmlhead/100_global.css and viola! You've just defined your default style for every page in your application. Further you can tweak the style for individual sections or pages easily (the same way) without making any modifications to your layouts, and for client customization requests, you can place the client's CSS in their own client-code directory, separate from your application!
So what do you do for the edge cases? If you need to deliver FlashPaper or PDF, then in the /_local/ directory you can simply set the value of tap.view.doc.format = "PDF" and the framework will convert the view to PDF. If you need to deliver some other content like an RSS feed, you can use conditional logic in your layout or simply use the CFCONTENT tag:
<cfcontent reset="true" variable="#tap.view.content#" />
One of the most popular notions to come out of the Fusebox community is the idea of an "exit fuseaction" (XFA). Of course the name "fuseaction" is part of that quirky private jargon of the Fusebox community and so other framework communities don't call it that, but nonetheless they generally use the idea. In other framework communities the same concept is called an "exit event" (XE) or an "exit event-handler" (XEH), simply because other framework communities generally use the term "event" rather than describing them as "fuseactions". See the jargon table. Below I'm going to refer to these collectively (irrespective of the jargon of a given framework) as "exit links" or maybe "exit navigation".
The premise is pretty simple. Let's say hypothetically that you have a simple search form for your web store that returns products from your catalog. Now in your web store, not all of the products are available for purchase -- some of them have been cancelled, discontinued or otherwise don't show up to customers. But of course you and your employees still need to see these products to manage them in the event that you need to change their status and put them "back on the shelf".
So you have a view template with your form in it, but where does the form post go? If the current user is a customer browsing the store, you probably want it to go to a different result page than if the current user is an employee managing the products.
You've got a couple options. You could copy and paste the form into a new view template so that you've got two separate search forms, one for employees and another for customers... but then that's good old-fashioned copy-paste coding and much of the point of doing all this with frameworks is to avoid all that by reusing code. The other option is to find some way to parameterize the form variables so that when the form is displayed it posts to different events (pages) based on the current context. So in the store administrator it posts to the "manage products" page and in the storefront it posts to the "browse products" page.
To be fair this actually isn't a specifically framework thing or even a specifically Fusebox thing. This wasn't a terribly uncommon practice even before Fusebox. What's different about it now is that the various frameworks or their developer communities have standardized the way they manage this technique. Before you might have seen a couple of different template that looked like this:
<cfset formaction = "manageproducts.cfm" /> <cfinclude template="../includes/ProductSearchForm.cfm" />
<cfset formaction = "searchproducts.cfm" /> <cfinclude template="../includes/ProductSearchForm.cfm" />
And then the form
<cfoutput> <form action="#variables.formaction#"> ... </form> </cfoutput>
Technically you've got a third option of putting the admin in a subdirectory and having the form post to a relative url so that the same file name "searchresults.cfm" exists in each directory (/store/ and /store/admin/) with different content. The only reason this approach (which is actually simpler) doesn't work with most frameworks is because they insist on pushing all requests through the same /index.cfm in the root. The onTap framework is the exception to this rule.
ColdBox: This framework has no special features to facilitate exit navigation. You simply set whatever exit variables you want in your event-handler and then output those variables in your view. Russ Johnson talked about this in some blogs he wrote about his experiences porting from ColdFusion to ColdBox in the latter part of 2007. He gave this example from one of his display templates:
<a href="?event=#rc.xehEditUser#&UserID=#rc.UserBean.getUserID()#">Edit User</a>
If you've been reading along by now you should know that it's common practice in the ColdBox community to set the variable "rc" to hold all the form and url variables from the event object, known colloquially as the "request collection". In older versions of Fusebox and in the onTap framework, this would be the "attributes" scope. This is a new practice I'm not personally fond of because I don't like seeing values I created mixed up with variables potentially supplied by the user (with the exception of parameterizing default values). If it's not a value the user is allowed to supply, I feel the attributes scope (or "request collection") is not a good place for it. (Similar to the way I like how custom tags encapsulate their own local variables so that there aren't namespace conflicts in the calling page, or using the "var" keyword for the same reason.)
But I digress... To have the value "rc.xehEditUser" in his view all he had to do was set it in his event-handler method like this:
<cfset rc.xehEditUser = "[handler].[event]" />
There you have it -- just as if there were no framework at all... almost.
Fusebox (traditional): Nick Tong wrote a short blog about the XFA verb in recent versions of Fusebox. Basically there's just an xml tag for setting them:
<fuseaction name="login"> <xfa name="login" value="login.authenticate"> <include template="loginform" /> </fuseaction>
<cfform action="#myself##xfa.login#" format="xml"> <input type="text" name="username" /> <input type="password" name="password" /> </cfform>
Fusebox (implicit): The form template doesn't change, but the variable is set using the method event.xfa().
<cffunction name="userlogin">
<cfargument name="myFusebox" required="true" />
<cfargument name="event" required="true" />
<cfset event.xfa("login","actUserLogin","more","variables") />
<cfset myFusebox.do("view.loginform") />
</cffunction>
The first argument to the xfa() method here is the name of the XFA, the second argument is the actual fuseaction it points to. In this example I've omited the name of the circuit from the name of the Fuseaction, so Fusebox will assume the current circuit (login) and built a full fuseaction name of "login.actUserLogin". The additional arguments can be whatever you would like them to be - simple name value pairs in order. These extra arguments were added to the method to allow the framework to build SES URLs (which isn't necessary with the onTap framework).
Mach-II This framework also has no built-in functionality for assigning exit navigation, however, a convenient approach to exit events in the xml config file is to filter the event arguments like this:
<event-handler event="widgetDetail" access="public"> <filter name="eventArgs"> <parameter name="xe.edit" value="widget.edit" /> <parameter name="xe.delete" value="widget.delete" /> </filter> <notify listener="widgetListener" method="getWidget" resultArg="details"/> <view-page name="widgetPage" /> </event-handler>
Model-Glue: This framework actually allows you to declare variables when you include a view template, which I personally find appealing as syntax goes, in spite of the fact that I don't care for XML configuration files. This makes the include similar in nature to a ColdFusion custom tag.
<event-handler name="login"> <broadcasts /> <results /> <views> <include name="body" template="loginform.cfm"> <value name="xe.login" value="actUserLogin" /> </include> </views> </event-handler>
onTap: This framework also doesn't have any features specifically related to exit nagivation, and I expect probably most folks who come to the onTap framework will end up adopting some practice of exit navigation. However this framework provides a number of tools that make it unnecessary to actually use exit navigation.
First the framework includes some URL management features which allow you to set "domains" within which URLs can be relative. So you could for example set a domain of "shop" or "store" and have that domain begin at http://www.mysite.com/store. Once the domain is set, you can create a link to any page in the store via the getURL function:
<cfset myURL = request.tapi.getURL("product.cfm?productid=5","store") />
You don't have to worry about where the current page is located, relative to the target page, because the framework already knows where the store is located. In other frameworks, requiring all requests to pass through the same /index.cfm template is what accomplishes this task of preventing misdirected paths, but it does so by restricting your choices in terms of directory structure and navigation and creates other problems related to SES URLs.
However the above function is somewhat cryptic isn't it? By that I mean, that I certainly remember it, but it requires a fair amount of typing, etc. So the framework's HTML library also offers some tools that use those URL management features in a more familiar way:
<cfset myVariable = "StuuuuperDuck!" /> <cf_html> <div id="menu" xmlns:tap="xml.tapogee.com"> <a href="?"> <tap:url name="[event]" value="myVariable" /> go somewhere </a> <a href="product.cfm" tap:domain="store"> <tap:url name="productid" value="5" /> For an ultra-close shave... </a> </div> </cf_html>
So in the two links you see here, the tap:domain attribute tells the framework that the URL should be relative to the store. Easy enough. Now the tap:url tag -- that actually behaves (or can behave) rather a lot like how exit navigation is used in other frameworks. Lets assume for a moment that you disagreed with me about having different files in the root and you actually want all your requests to go through /index.cfm. In that case you would have to supply an [event] name for every url and you could easily place a tap:url tag inside of any link where you wanted to append a particular variable. Just put the name of the URL parameter in the name attribute and the name of the variable to append to the url in the value attribute. So when it builds that url, it looks like this:
<a href="?[event]=StuuuuperDuck!">go somewhere</a>
Moreover consider this. You have the login form described before:
<cf_html return="tap.view.loginform"> <tap:form action="somepage.cfm" xmlns:tap="xml.tapogee.com"> <input type="text" name="username" /> <input type="password" name="password" /> </tap:form> </cf_html>
Because of the way the HTML library works, it's not necessary to set the action of this form before hand - you can actually include the form first and then set its action afterward!
<cfinclude template="theform"> <cfset request.tapi.html.attribute(tap.view.loginform,"action","otherpage.cfm") />
Frameworks have it ... usually... I'll get back to you on this one... hit me up on a later version of this article.
The rule of thumb is there's no such thing as a "unique" feature. There are "currently unique" features -- and they may stay unique -- but the point is that any of these things could be ported to another framework or application. I haven't included "aspect programming" (AOP) here because as far as I know that's provided by ColdSpring, which can be used with any of these frameworks. Similarly of the frameworks I've included, Fusebox is the only one that doesn't have some kind of caching feature(s) in a current or future release, which is why I haven't described "caching" as a unique feature for any of them.
I do want however to make one other comment about these feature lists, aside from pointing out that they're probably incomplete. The goals of each framework are often rather different. I've made no attempt to disguise the fact that the onTap framework is intended to be a "full stack" framework similar to Ruby on Rails, in that it includes features to help speed and improve development at every layer of your application (model, view, persistence), not just the controller. By comparison, Peter Farrell and the others responsible for developing Mach-II have the exact oposite as their goal. Their framework is focused purely on the controller and they intentionally avoid adding features they feel would be best described as "full stack" because their goal is to keep Mach-II small and focused.
So it's really not entirely fair to look at the list of "features" the same way you might listen to Don Pardo announcing a contestant's winnings on the Price is Right. For that matter, being small and focused in itself could be considered a "feature" of Mach-II, like the Apple commercial in which the PC walks on to the stage inflated like a giant beach ball, complaining about all the extra software installed by the vendor and slowing him down. If you want a small and focused, "grab it and go" framework, then Mach-II would be a good choice.
Did I leave something out? Let me know so I can update this list.