Mach II Development Guide

Release 1.2.1 (4/14/2005)

Contents

This Development Guide is structured as follows:

Introduction

This document explains how to develop applications using Mach II (Release 1.0.10), an MVC-based framework that implements the Implicit Invocation Architecture. You can read more about Mach II on the official Mach II website and Sean Corfield's unofficial Mach II website as well as Phil Cruz's Mach II information website. It is a living document, growing over time, incorporating feedback from Macromedia Web Team developers as well as the larger ColdFusion community, as the guidelines are 'proved' in our live environment.

Although this document is published as-is for the ColdFusion community, Macromedia's Web Team own the document and decide what goes into it. You may, however, take a copy of this document and modify it as you see fit to create your own development guide as long as you acknowledge this original document.

Conventions

The following conventions are used in this document:

[TBD: some comment in blue]
This indicates an issue which needs discussion and agreement within WTG.
Some comment crossed out in gray
This is a Macromedia-specific issue which is provided as an example of how the Macromedia Web Team handle an issue.

Concepts & Core Files

This section explains the concepts in the framework and describes how to access the core files. Much of this information is taken from my Mach II Concepts page.

MVC - Model-View-Controller

The first concept is MVC - Model-View-Controller - where the presentation (View) is completely separated from your business logic (Model) and interactions between the two are handled by Mach II itself (Controller). The Views are simply .cfm files that render HTML - presentation code - and can make use of request scope variables and an event object (set by Mach II - more below). The Model is a set of ColdFusion Components (CFCs) that represent all the logic of your application, from the database interaction to the business rules and decisions. The Controller is, essentially, Mach II itself and the parameters of the Controller are defined by the mach-ii.xml file.

Mach II has a slight grey area between the Model and the Controller. The framework has the concept of "listeners" which are CFCs that extend the framework and provide the glue between the core Controller and the core Model. These listener CFCs can be considered part of the Model or part of the Controller depending on how you like to structure your applications but best practice suggests you should keep your Model independent of the framework (so that it is easier to reuse for Web Services, Flash Remoting or even within another framework). The section on Designing Models has more information about this.

Implicit Invocation Architecture

The second concept is the Implicit Invocation Architecture on which Mach II is based. You can read more about this on the Mach II website (Ben Edwards has a great paper on this). The basic premise is that events are generated (by the user or by the application) and they are handled by, well, event handlers. The event handlers respond to the events by performing business logic and sometimes generating more events. Mach II keeps track of the active events and processes them, invoking listeners as necessary and rendering views until each each request is fully processed.

Design Patterns

These guidelines mention several design patterns (e.g., Session Façade, Memento, Transfer Object). Here are some good references on design patterns:

Mach II Core Files

The framework is a collection of ColdFusion Components under the MachII directory. That directory can be installed under your ColdFusion document root or on a Custom Tag Path defined in the ColdFusion Administrator. At Macromedia, the MachII directory is under CVS at:

{cfmxroot}/extensions/components/MachII/

Your application index.cfm file (under {cfmxroot}/wwwroot/{appname}/) simply includes the mach-ii.cfm core file (using a mapping to the MachII directory, if Mach II is installed outside the web root):

<cfinclude template="/MachII/mach-ii.cfm" />

Prior to including the mach-ii.cfm file, you can set your own values for MACHII_CONFIG_MODE, MACHII_CONFIG_PATH and, if needed, MACHII_APP_KEY. By default, the mach-ii.cfm core file uses the directory in which the request originated ({appname} in our example) as a key into application scope to store that application's instance of the framework objects. That can be overridden by setting MACHII_APP_KEY in index.cfm (but is not generally recommended).

The mach-ii.cfm core file by default looks for ./config/mach-ii.xml (relative to the {appname} directory), which it uses to initialize the framework for that application. That path can be overridden by setting MACHII_CONFIG_PATH in index.cfm. For security purposes, it is recommended that you place your mach-ii.xml file outside your document root so that it cannot be accessed through a web browser. At Macromedia, we override MACHII_CONFIG_PATH as follows:

<cfset MACHII_CONFIG_PATH = expandPath("/environment/{appname}/mach-ii.xml") />

Remember that /environment is a mapping to {cfmxroot}/config/target/ - a directory created by our build system - so the actual configuration file, under CVS, is {cfmxroot}/config/development/{appname}/mach-ii.xml and can be overridden for staging, integration and production if necessary although this is not recommended (we have better mechanisms for handling environment-specific variables).

Note: For CFMX 6.1, expandPath() with a mapping works on Unix (Solaris and Mac OS X) but does not work on Windows - expandPath() does not process the mapping. This has been fixed in CFMX 7.

MACHII_CONFIG_MODE is set to 0 (dynamic) by default in the core files (since release 1.0.8). This causes Mach II to dynamically reload the framework only if mach-ii.xml changes. This is good for development but it can also be useful to add the following code to index.cfm:

<cfif not request.productionMode and
	structKeyExists(URL,"reloadApp")>
	<cfset MACHII_CONFIG_MODE = 1 />
</cfif> 

This allows you to force a reload easily during development by adding ?reloadApp to the URL. In production, you'd normally set MACHII_CONFIG_MODE to -1 (never). Here's a quick summary of the impact of each of the three modes:

At Macromedia, we set a default for MACHII_CONFIG_MODE in sitewideconstants.cfm to dynamic for development and QA environments and to never for production environments. The standard mach-ii.cfm file uses this default (request.MachIIConfigModel) if it is defined and you have not already overridden MACHII_CONFIG_MODE in your index.cfm file.

Application Structure & Design

This section explains how to structure your application and provides some general design guidelines for building object-oriented applications.

Application Structure

This section explains how to structure your application to fit both Mach II and our standard directory structure guidelines.

mach-ii.xml

This has already been discussed under Mach II Core Files. This is the central configuration file for a Mach II application that specifies all the listeners, events and views etc. As mentioned above, it lives outside the document root.

The model, filters & plugins Directories

This is a directory structure convention used by Mach II to separate the CFCs used by an application. Although it is not required to follow the Mach II convention, it is actually fairly convenient. Under your application components directory, create these three directories: model, filters, plugins. At Macromedia, all our components live outside the document root so we end up with this structure:

{cfmxroot}/extensions/components/{appname}/model/
{cfmxroot}/extensions/components/{appname}/filters/
{cfmxroot}/extensions/components/{appname}/plugins/

CFCs in the model directory are a combination of listeners that extend MachII.framework.Listener and framework-neutral business model components. CFCs in the filters directory are event filters that extend MachII.framework.EventFilter. CFCs in the plugins directory are plugins that extend MachII.framework.Plugin. Other CFCs that are used to support the application can live in either the model directory or if appropriate in another more suitable directory, e.g., when wrapping existing application CFCs in a Mach II application skin.

To keep the model directory organized, it may be a good idea to create separate sub-directories within it for the listeners if you have several of them, or even create a listeners directory directly under your {appname} directory:

.../components/{appname}/model/listeners/ - just the listeners
.../components/{appname}/model/ - other, framework-neutral, business objects

or:

.../components/{appname}/listeners/ - just the listeners
.../components/{appname}/model/ - other, framework-neutral, business objects

Another possibility is to keep your listeners and business objects together but clearly identify your listeners through a naming convention, e.g., SomethingListener.cfc.

Which approach you choose will likely depend on how many listeners your application has and whether or not you are reusing the business objects in another context.

The views Directory

Another directory structure convention, this is used to separate out the .cfm files that render HTML. These .cfm files live in the views directory underneath the application directory, either under the document root or under the include directory for that application. At Macromedia, we put our views outside the document root:

{cfmxroot}/extensions/includes/{appname}/views/

Since we have a mapping from /cfinclude to {cfmxroot}/extensions/includes/ we define the global Mach II property applicationRoot, in mach-ii.xml, to be "/cfinclude/{appname}" instead of just "/{appname}" so that Mach II can find the view .cfm files.

Application Design Considerations

This section provides guidance on designing your Mach II application. We start with some general design guidelines in this section and then subsequent sections cover models - including design patterns - then views, event handlers, event filters and finally plugins.

Overall Design Considerations

You can approach the design of a Mach II application from two different view points: from the business model or from the user experience. In most cases, you will be able to perform a certain amount of the design in parallel in these two areas. As you will see in the sections below, the business model components can be designed and implemented independently of the rest of the application (and usually independently of the Mach II framework itself). The user interface can also be designed and - for the most part - implemented independently of the rest of the application. The interactions in the user interface drive the design of the event model in a Mach II application which then, in turn, drives the design of the event handlers and listeners, constrained by the interface of the business model. For teams where the user interface work and back end engineering work are handled by separate individuals or groups, this allows for more efficiency early in the project lifecycle. Furthermore, the loose coupling encouraged by Mach II allows for more flexibility and easier maintenance, later in the lifecycle.

Some Basic Object-Oriented Design Guidelines

For many developers, especially those new to Object-Oriented Design, the hardest part of building an application in an OO style is determining which components are needed. If you can write a short coherent narrative that describes the interactions in your business logic, you'll find that most of the nouns in that narrative suggest themselves as components and most of the verbs suggest themselves as methods on those components. Most - not all. Remember that not everything needs to be a component - some things simply aren't important enough in the overall business model to require a component to model their behavior. In one application, a person's name might be important enough to be modeled as a component that understands title, greeting, first (given) name, family name, middle name or initial, suffix etc but in another application it may suffice to have a string for full name (or perhaps two strings for first and last name). There are no hard and fast rules.

Once you have your components identified, you need to consider how well-defined they are. Each component should be highly cohesive - it should do (or represent) one "thing" only and do it well. The components should also be loosely coupled - they should not need to know about or depend on each other in order to do their "one thing". Be prepared to refactor your code to improve the cohesion and reduce the coupling - your first choice of components may be obvious but won't necessarily be the best representation of your business logic. If you find it hard to represent a relationship between two or more components, perhaps the relationship itself is sufficiently important to need its own component - sometimes the relationship may actually be more important than the entities it connects. Also remember that a relationship between two components represents coupling between those components. Abstracting the relationship into its own component can often reduce the coupling between other components and so provide more flexibility.

The other key point to consider is inheritance versus composition. Many OO novices see inheritance everywhere and try to have components extend other components left, right and center. Inheritance is a very special relationship between components: it means "is-a". It really isn't as common as most people seem to think and it is also a form of very tight coupling between the parent and child components. Unless the relationship genuinely represents an immutable "is-a" relationship - where a child component instance is wholly substitutable for the parent component instance in every use case - then you should consider some form of composition instead. Composition can be broadly described as "has-a" or "refers-to" relationships. A good example of this is the Employee / Manager model. Novices often try to model this with inheritance - a Manager "is-a" Employee - but this breaks down when you start to consider promotions and substitutions and so on. A more accurate model is that an Employee "has-a" Role and Manager "is-a" Role. When a regular employee is promoted, it is only really their role that changes and, in some companies, an employee can actually have multiple roles, especially if the company has a "matrix reporting" system or bestows temporary roles on employees.

Good Object-Oriented Design takes time and practice so leverage other people's work by learning about and using Design Patterns - they are your friend!

Designing Models For Mach II

This section looks at design issues in the Model portion of your application. The Model encompasses all of the business logic in your application and is implemented as a collection of ColdFusion Components.

Separation of the Business Model and Mach II

Mach II interacts with the Model portion of your application using components that extend MachII.framework.Listener. In a simple application, all of your business logic might be implemented in such listeners but this approach does not scale well with increasing application complexity because, amongst other things, it introduces a tight coupling between the elements of your business logic and the Mach II framework. If you've carefully designed your business components based on the guidelines in the previous section, it should be clear that only a few of those components are natural "listeners" for events. It might even be that none of your basic business components are natural "listeners". This is not a problem!

You are much better off ensuring there is only a thin layer of coupling between the framework and your application - if necessary, create new components that extend the Mach II listener component, whose sole purpose is to communicate those events to the components within your business model. In other words, try to isolate your business components from the framework components as much as possible: only listener components should know about the framework; only listener components should access Mach II properties; only listener components should announce events. The core business components should know nothing about Mach II and have no dependencies at all on the framework.

Anatomy Of A Listener

A minimal listener CFC that uses the CFCInvoker_Event invoker type looks like this:

<cfcomponent extends="MachII.framework.Listener">
	<cffunction name="configure" returntype="void" access="public" output="false">
		<!--- perform any initialization --->
	</cffunction>
	<cffunction name="someMethod" returntype="someType" access="public" output="false">
		<cfargument name="event" type="MachII.framework.Event" required="yes" />
		<!--- perform some task --->
		<cfreturn someValue />
	</cffunction>
</cfcomponent>

The method, someMethod, can be invoked from an event handler in mach-ii.xml using the <notify> command. The returned value, someValue, should be of type someType. Note that someType can be void if the method does not return anything - either it does not contain a <cfreturn /> tag or it specifies no expression in <cfreturn />.

The listener is declared in mach-ii.xml as follows:

<listener name="listenerName" type="Path.To.YourListener">
	<invoker type="MachII.framework.invokers.CFCInvoker_Event" />
</listener>

The <invoker> tag specifies how the methods should be invoked on the listener. In general, you should specify CFCInvoker_Event as shown above which will cause the current event object to be passed as a single argument to the invoked method. You can also specify CFCInvoker_EventArgs which will cause the current event's arguments to be passed individually as named arguments into the method. See the section on Invokers & Listeners that follows for more detail.

You may optionally specify parameters for the listener declaration:

<listener name="listenerName" type="Path.To.YourListener">
	<invoker type="MachII.framework.invokers.CFCInvoker_Event" />
	<parameters>
		<parameter name="param1" value="value1" />
		<parameter name="param2" value="value2" />
	</parameters>
</listener>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the listener using the getParameter() method, e.g., getParameter("param1") (the listener inherits this method from the Listener base class).

The methods of a listener CFC can be invoked in an event handler as follows:

<notify listener="listenerName" method="someMethod" resultKey="someVariable" />

This causes the someMethod() method to be called with the current event and the returned result to be stored in someVariable, e.g., request.result. The resultKey attribute is optional (and should not be specified for methods that have returntype="void").

A listener has access only to the current event although it can announce additional events to be added to the queue (and which will be executed at some point after the current event has been handled) using announceEvent(). A listener method typically does its job by calling one or more methods on one or more business model objects. The business model objects might be created on the fly, might be managed in session scope or might be created within the listener's configure() (and stored in the listener's variables scope).

For more information on writing listeners, refer to the Mach II article How to... Develop Listeners by Ben Edwards.

Invokers & Listeners

When a listener is declared in mach-ii.xml, you have to specify an invoker type. Mach II provides two default types:

The invoker affects how arguments are passed to the listener methods in a <notify> tag and what happens to the result. As noted above, CFCInvoker_EventArgs causes all the event arguments to be passed to the method as separate named arguments. CFCInvoker_Event causes the whole event object to be passed as a single argument (of type MachII.framework.Event) to the method. Both invokers store the result in the specified variable, which means you have to use a request scope variable. To some extent, which you use is a matter of style but there are some practical considerations:

If you use CFCInvoker_Event then your listener methods are passed a single argument which should be declared as follows:

<cfargument name="event" type="MachII.framework.Event" required="true"/>

You can test whether a given URL / form variable was provided using the isArgDefined() method on the event:

<cfif arguments.event.isArgDefined("anArg")>
	...
</cfif> 

Since URL / form variables are inherently strings (as far as HTTP is concerned), you are better off explicitly validating the event argument types in your own code, rather than trying to use CFCInvoker_EventArgs and declaring method arguments for individual URL / form variables (you don't really want ColdFusion to throw an exception if a user mistypes something in an input field!).

You can also create custom invokers if you need a different behavior such as this invoker to put the result directly into the event object, setting the event argument specified by the resultKey= attribute:

<cfcomponent extends="MachII.framework.ListenerInvoker" output="false"
		hint="I am a custom invoker" displayName="EventInvoker">
	<cffunction name="invokeListener" access="public" returntype="void" output="false"
			hint="I implement the method invocation for a listener">
		<cfargument name="event" type="MachII.framework.Event" required="true" 
				hint="I am the current event" />
		<cfargument name="listener" type="MachII.framework.Listener" required="true" 
				hint="I am the listener that is being notified" />
		<cfargument name="method" type="string" required="true" 
				hint="I am the method that is being invoked" />
		<cfargument name="resultKey" type="string" default="" 
				hint="I name the optional event argument in which the result is stored" />
		<cfset var resultVar = 0 />
		<cftry>
			<cfinvoke component="#arguments.listener#" 
				method="#arguments.method#" 
				event="#arguments.event#" 
				returnVariable="resultVar" />
			<cfif arguments.resultKey is not "">
				<cfset arguments.event.setArg(arguments.resultKey,resultVar) />
			</cfif>
		<cfcatch type="Any">
			<cfrethrow />
		</cfcatch>
		</cftry>
	</cffunction>
</cfcomponent>

Instance Data & Listeners

Since Mach II loads all the framework component instances into application scope, you need to bear in mind that any instance data you create in your listener will effectively be stored in application scope. That has three main implications:

In general, your listeners should be stateless - with no instance data - unless you are specifically caching data for performance reasons, e.g., saving property values in variables scope to save accessing the properties dynamically in each request. Consequently, you should take extra care to use var to declare all local variables in listener methods so that you don't accidentally store something in the unnamed scope! Remember that tags like <cfquery> create variables too so you must var-declare those as well:

<cfset var userSelect = 0 />
<cfquery name="userSelect" ..>
	...
</cfquery>

Session Façade

As mentioned above, if you need to manage per-session data in your application, you will need to use the Session Façade design pattern. The way this works is that only your listener is session-aware, i.e., it knows about session scope and manages component instances that live in session scope but those per-session component instances are not listeners themselves (they do not extend MachII.framework.Listener) and they do not reference session scope. For example, a shopping cart listener would respond to events like 'addItem' and 'removeItem' and 'updateQuantity' but it would delegate the actions to a cart object that is stored in session scope. The shopping cart listener would create the cart object in session scope on demand. The cart object would store information as instance data (which is per-session because the cart is per-session) but would not reference session scope.

<cfcomponent extends="MachII.framework.Listener" ..>

	<cffunction name="addItem" ..>
		<cfargument name="item" ../>
		<cfset getCart().addItem(arguments.item) />
	</cffunction>

	...

	<cffunction name="getCart" returntype="Cart" access="private" ..>
		<cfif not structKeyExists(session,"cart")>
			<cfset session.cart = createObject("component","Cart").init() />
		</cfif>
		<cfreturn session.cart />
	</cffunction>

</cfcomponent>

The partial example above demonstrates the Session Façade technique but is not intended to be production quality (for example, there's no configure() method for the listener, there's no hint= or output= attributes, it doesn't lock session scope when creating the cart object which might be needed if you are concerned about threaded access for a single user - which may or may not be a concern for your application).

Transfer Objects

The basic event lifecycle in Mach II is:

If you need more than one piece of data in your view, you could notify the listener multiple times, creating several resultKeys and having the view depend on several request scope variables. It seems like the obvious approach so what is the impact of doing this?

These should raise red flags - especially the last two, which cause encapsulation to break down and coupling to increase. When are you likely to see this pattern occurring in your application? A good example is a view that displays information about a person: the view probably needs to display first name, last name, street address, city, state, zip etc. The obvious - naïve - approach would be to have getter methods for each of these on the listener (with calls for each of these in the event handler) and have the view depend on request.firstName, request.lastName, request.streetAddress etc. Ugly.

The Transfer Object design pattern is intended for situations like this and solves the problem by aggregating data that is passed between the model and the view. In the above example, you would have a single getPerson() method which returns a struct (that contains all of the data needed) and the view would then have a single dependency on that one resultKey. If you want a little more encapsulation on your transfer object, you could use a bean - a simple CFC that has getters and setters for the data and a constructor (init()) - which the listener component uses to set all the data in the transfer object before returning it to Mach II.

Beans & Form Handling

As indicated above, a bean is a simple CFC with getters and setters that encapsulates some data (properties). Beans are typically used as Transfer Objects to pass data between different layers in an application. If a bean has a property foo (a private instance variable) then it also has methods getFoo() and setFoo() to get and set, respectively, the value of foo. The getFoo() method will be public, the setFoo() method may be public or private depending on whether the bean is considered read-only or read-write. Here is a simple read-only bean:

<cfcomponent>
	<!--- "declare" properties for clarity: --->
	<cfset variables.foo = "" />
	<!--- constructor: --->
	<cffunction name="init" returntype="FooBean" access="public" output="false">
		<cfargument name="foo" type="string" default="" />
		<cfset setFoo(arguments.foo) />
		<cfreturn this />
	</cffunction>
	<!--- public getters: --->
	<cffunction name="getFoo" returntype="string" access="public" output="false">
		<cfreturn variables.foo />
	</cffunction>
	<!--- private setters: --->
	<cffunction name="setFoo" returntype="void" access="private" output="false">
		<cfargument name="foo" type="string" required="yes" />
		<cfset variables.foo = arguments.foo />
	</cffunction>
</cfcomponent> 

A read-write bean differs only in that the setters are public. The constructor has an optional argument for each property and calls setXxx() for each property xxx.

Mach II supports bean creation and population through the <event-bean> command:

<event-bean name="beanName" type="beanType" fields="field1,field2" /> 

This command creates a bean of the specified type (beanType, e.g., my.model.FooBean) and stores it in the current event object as an event argument called beanName (e.g., fooBean or just foo).

This makes it very easy to handle form submissions in Mach II: define a bean component to represent the data in a form and use the <event-bean> command to assemble it from the form data submitted. Then you can operate on the submitted data as an encapsulated bean, performing validation (see Designing Event Filters) and persistence and whatever else you need.

For more information on writing and using beans, refer to the Mach II article Beans, Beans, the Musical Fruit by Ben Edwards and Hal Helms.

Database Access Objects

Whilst this topic is not directly related to the Mach II framework, most applications need to implement data access (usually to a database) and many people ask for guidance on this subject.

There are two basic patterns of access to persistent data within most applications:

ColdFusion has a great built-in idiom for dealing with the first type of access - the query - which is an efficient way to manipulate (potentially large) sets of data rows retrieved from a database (or other data sources, since you can easily create a query object and populate it with your own data). When you are dealing with aggregated access, it does not make sense to convert every row returned into a fully-encapsulated object (CFC instance) when all you are likely to do with the data is display a few fields with a link to a detail page that will focus on the selected row.

On the other hand, when you are focusing on a single row it usually does make sense to work at the fully-encapsulated object level since you are usually interested in object behavior at that point. This is also the level where you need the standard CRUD (Create, Read, Update, Delete) operations.

Recognizing these two basic patterns, you should design your components accordingly by providing separate components for each pattern. This is best explained through an example:

Separating out these operations from the business model object helps it stay persistence-neutral. The gateway components can be optimized for retrieving large record sets, caching etc. The DAO components can be optimized for dirty data updates, pooled object access and so on. Again, the details of these optimizations are beyond the scope of this document but providing for the two distinct patterns of data access will get you started on the right road.

As noted above, you can implement the CRUD methods directly in your business model object if you wish, although these guidelines recommend using the separate DAO component outlined above. The pros of implementing CRUD in your business model object are:

The cons are:

As you can imagine, it is the driving forces of good encapsulation, high cohesion and loose coupling that lead to the recommendation above to provide gateway and DAO components that are separate from the business model components.

To see more concrete examples of these concepts, visit mach-ii.info and download the sample application which uses a number of these design patterns.

Designing Views For Mach II

This section looks at design issues in the View portion of your application. The View encompasses all of the HTML user inteface in your application and is implemented as a collection of .cfm pages.

Controller / View Data Transfer

Since views should not contain any logic (beyond that necessary to iterate over data structures to display them as lists), any dynamic portions of a view should be passed in from the controller (Mach II), either as request scope variables or via the event object (accessible in a view as just plain event). The section on Transfer Objects touches on this topic and recommends that views depend on as few variables as possible to maintain encapsulation and reduce coupling.

The key thing to remember is that the request scope variables and the arguments in the event object that are used by a view define the API between the controller and the view and thus specifies a contract that should be agreed early on in the design, documented and then honored for rest of the project lifecycle if possible. This helps maintainability and stability.

You can reduce that API down to just the current event object by moving the request scope data into the event object:

<event-arg name="foo" variable="request.foo" />

Whether you do this or not depends on whether you feel the increased number of <event-arg> commands is worth the increase in encapsulation. An alternative is to use the custom invoker, EventInvoker, shown in the Invokers & Listeners section of Designing Models. Our rule of thumb is to leave data where it naturally falls rather than move it around artificially just to increase encapsulation. In other words, we use request scope in views for data that already happens to be in request scope (e.g., as set up by contentKey=) and we use the event object for data that already happens to be in the event object (e.g., URL or form parameters, values explicitly set with <event-arg> or event.setArg() inside filters, listeners and plugins or via EventInvoker notify commands).

Parameterized Views and eXit Events (XEs)

A good use of event arguments in a view is when <event-arg> is used to set values for parameterized views, such as forms that support both edit and create operations (typically their submit and cancel actions are passed in as well as the submit button label). This is much better than hard-coding the submit and cancel actions into the view itself.

Fusebox uses a similar technique and refers to it as XFAs - eXit FuseActions - so by analogy, these could be described as XEs - eXit Events. Again, it reduces coupling between views and the application control flow so it's good practice to parameterize views where appropriate so that the exit paths out of a view (links and form actions) are passed in as event arguments. For example, a paged record set view might have previous / next page links like this:

<a href="index.cfm?event=#event.getArg('XEPrevious')#">Previous</a> |
	<a href="index.cfm?event=#event.getArg('XENext')#">Next</a>

This allows the view to be invoked in different parts of the application with the previous / next events passed in from <event-arg> commands in the mach-ii.xml file.

contentKey Considerations

Mach II allows views to be rendered into contentKey variables so that a single HTML page can be constructed from multiple views (page fragments). These contentKey variables are typically in request scope so they also form part of the API between the controller and the views, as discussed above. Care therefore needs to be taken in planning these too. As a general guideline, you will produce a more maintainable application if you can arrange it so that dynamic data (request scope variables and / or the event object) is passed into simple views that are rendered into contentKey variables and then those contentKey variables are aggregated into a single layout template view. For example:

<event-handler event="showFoo" access="public">
	<notify listener="foo" method="getFoo"
			resultKey="request.fooData" />
	<view-page name="fooPage" contentKey="request.content" />
	<view-page name="mainLayout" />
</event-handler>

In this example, the API between fooPage and the controller is simply request.fooData (which might be a struct containing multiple items) and the API between mainLayout and the controller is simply request.content. There is no dependency between mainLayout and any dynamic data - all dynamic data is rendered into HTML by other views prior to rendering mainLayout.

If you have several views that go together to make up part of a layout you can sometimes simplify your event handler by appending to a contentKey (this is new in 1.0.10):

<event-handler event="showFoo" access="public">
	<notify listener="foo" method="getFoo"
			resultKey="request.fooData" />
	<view-page name="fooPod" contentKey="request.content" />
	<view-page name="barPod" contentKey="request.content" append="true" />
	<view-page name="mainLayout" />
</event-handler>

In this example, the output of barPod is appended by Mach II to the output of fooPod within request.content so you do not need additional contentKeys and layouts.

The intent here is for each view to focus on rendering just the data that it depends on and using a layout view to assemble the finished page from pre-rendered HTML fragments. This keeps each view cohesive: each view does one job well. It also reduces coupling between the views themselves and between the views and the controller. The next section discusses this principle in a bit more detail.

Portals a.k.a. Grid Layouts

In a typical application the user interface is quite complex and often has several dynamic elements. The navigation can be dynamic, for example, adapting to where you are in the flow of the application. Examine your user interface with an eye to breaking it down into smaller, simpler parts that form a grid. Most portal sites (e.g., My Yahoo!) are good examples of grid layouts. They have multiple columns, each containing multiple sections (often called pods).

Implement each of these pods as a separate view that renders a request scope variable or some event arguments into a contentKey and use a layout view to assemble those contentKey variables into the finished HTML page. This will produce small, focused (cohesive) views that are loosely coupled. You are also more likely to be able to reuse user interface elements on other pages - because you can reuse views very easily in Mach II as long as they have only narrow, well-defined dependencies. Even if these pods contain only static content, it is still usually better to implement them as views so that you can rearrange page elements more easily - because you only have to change the layout view. This is sometimes referred to as "view stacking" but is really an implementation of the Composite View design pattern (see J2EE pattern description).

This raises the question of whether headers and footers should be implemented as separate pod views and assembled by the layout view? If the header and footer are site-wide elements, the chances are that they will only change if the whole look and feel of the site changes. Implementing them as separate views is possible but then every single event-handler that generates HTML will look like this:

<event-handler event="..." access="...">
	...
	<view-page name="..." contentKey="request.content" />
	<view-page name="header" contentKey="request.header" />
	<view-page name="footer" contentKey="request.footer" />
	<view-page name="mainLayout" />
</event-handler>

Or, to reduce code duplication in mach-ii.xml, you could do this:

<event-handler event="..." access="...">
	...
	<view-page name="..." contentKey="request.content" />
	<announce event="layoutPage" />
</event-handler>


<event-handler event="layoutPage" access="private">
	<view-page name="header" contentKey="request.header" />
	<view-page name="footer" contentKey="request.footer" />
	<view-page name="mainLayout" />
</event-handler>

Every event-handler that needs to generate HTML would announce the layoutPage event. If you are comfortable with this style, feel free to implement headers and footers as separate views. If you feel that headers and footers are so closely tied to the overall page layout then either implement them directly inside the layout view or implement them as separate files and include them into the layout view:

<!--- doctype etc goes here --->
<html>
<!--- head / title etc go here --->
<body>
<cfinclude template="header.cfm" />
#request.content#
<cfinclude template="footer.cfm" />
</body>
</html>

At Macromedia, we use custom tags to control the overall rendering of pages so our layout view looks somewhat simpler:

<!--- set up page settings etc --->
<cfimport taglib="/customtags/mmlf/" prefix="mmlf" />
<mmlf:renderpage>
#request.content#
</mmlf:renderpage>

Views And Scopes

If you've followed the recommendations above, your view will contain references to a few request scope variables and / or the current event object (event). That is effectively the interface between the controller and that view. What about using other scopes inside views?

Designing Event Handlers For Mach II

This section looks at design issues in the event-handlers section of mach-ii.xml. If you've followed the Overall Design Considerations, you will have designed (and probably implemented) both your core business model components and your HTML views by the time you started designing your event handlers. In terms of designing the views, simple interaction schematics should be a sufficient level of detail to enable you to start work on designing the event handlers.

Every user interaction corresponds to an event: every link and every form submission is an event, including the default event implied by the first page a user requests. Since the public event names will be a visible (user-facing) part of your application, it's worth choosing readable event names of the form verbNoun. Use mixed case for event names - it makes them more readable. Event names are not case sensitive in Mach II so it doesn't matter if a user mistypes such a URL as verbnoun. An acceptable alternative is to use verb.noun (which might be more familiar for developers who are used to Fusebox, which uses circuit.fuseaction).

The events implied by user interactions form the first tier of events in your application. The essence of Implicit Invocation is to abstract the control flow between components out into the event model, thus you will find a large number of events are implied by your application logic. For example, whenever you handle an event that has conditional flow, such as login which can succeed or fail, you should usually create event handlers for each of the conditional outcomes - see Application Neutral Events below. Even when the flow is linear, it is often worth separating out the initial user event processing from any subsequent application flow such as view rendering. Event handlers should generally be short and self-contained - again, this is driven by the desire to have high cohesion and loose coupling. Name the events to reflect this internal partitioning, e.g., sectionVerbNoun or section.verb.noun.

On the other hand, don't break event handlers up unnecessarily - if you have a sequence of listener method calls that belong together, use <event-arg> to chain listener calls rather than breaking up the sequence into a series of artificial private events. You can use <event-arg> to move request scope data into the event object like this:

<notify listener="myListener" method="methodOne" resultKey="request.result" />
<event-arg name="oneArg" variable="request.result" />
<notify listener="myListener" method="methodTwo" resultKey="request.result" />

Public & Private Event Handlers

The events caused by user interactions are only part of the picture in a Mach II application - they are the public events, generated by external stimuli: users. The event handlers for these events should be declared access="public". All other event handlers - those that handle events generated by the application itself - should be declared access="private". The event handler for the default event - the one that is invoked when the user does not explicitly specify an event in the URL - should be declared access="public".

Declaring event handlers private ensures they cannot be invoked via URL hackery. That means that you only need to worry about user input data validation in public event handlers and then set up event data before handing off control to internal (private) event handlers. Use event filters for validation as well as manipulating event data - see the section on Designing Event Filters For Mach II.

In most OO programming languages, it is generally recommended to declare all public entities first with private entities last. That is also good practice to follow for event-handler declarations, with each group in alphabetical order so that event handlers are easier to find within long mach-ii.xml files.

Application-Neutral Events

If your listener needs to announce events, choose event names that suit the listener rather than the application as a whole, i.e., use application-neutral events. You can use the event-mapping tag to map the listener's events to those recognized by the application. This is once again driven by the concepts of cohesion and coupling to improve reuse and maintainability: using application-neutral events helps decouple your listener from the application that uses it and may allow for the listener to be reused in other applications.

For example, a login listener should announce loginSucceeded or loginFailed (or more specific failures such as loginBadPassword, loginNoSuchUser etc) and then the event handler should map those to whatever the application needs:

<event-handler event="adminLogin">
	<event-mapping event="loginSucceeded" mapping="showAdmin" />
	<event-mapping event="loginFailed" mapping="showLogin" />
	<notify listener="adminListener" method="login" />
</event-handler>

In this example, the login() method of adminListener would announce the application-neutral event loginSucceeded or loginFailed - using announceEvent() - but the event mappings would cause this to be translated so that what is placed in the event queue is showAdmin or showLogin respectively.

The event mappings are active only from the <event-mapping> command to the end of the event handler (and therefore the <event-mapping> command must precede the listener notifications that it is intended to influence).

Handling Shared Execution Paths

It is quite common to see similar code across several event handlers as you are developing your application. For example, Edit and Create operations tend to have a similar flow beyond the initial capture / load of data (as you can see in the ContactManager sample application). Building complex layouts is another operation that tends to create common code at the end of several event handlers. In each of these cases consider moving the common operations into a separate event handler and simply announcing a new common event from each of the original event handlers.

Even if the code isn't identical, you can often abstract out the variables in it and turn it into common code. In that situation, your original event handlers will perform a certain amount of setup, e.g., using event-arg to define values that differ between the two code paths, and then announce the new common event.

In both cases, the common event should be declared access="private", following the guidelines above. The intent here is to reduce code duplication and to look for reuse.

Decomposing and Chaining Event Handlers

If you separate an event handler into multiple private event handlers, you need to think about how to communicate data between them:

<event-handler event="processForm" access="public">
	<announce event="handleFormData" />
	<announce event="displayResult" />
</event-handler>

Since handleFormData and displayResult are both announced from the processForm event handler, they are both new event objects which both have a copy of the event arguments from the processForm event object. However, any changes made to the event object inside the event handler for handleFormData will not be reflected in the event object that is passed to the displayResult event handler - they are distinct events. That means that communication between those two event handlers must be done through request scope variables which is less than ideal.

What is really needed here is the ability to "chain" the events together - in other words to make the sequence that is implicit in the above code into an explicit sequence. Why is the sequence implicit above? Because it relies on the event queue - to process handleFormData first and then displayResult second. A more explicit solution would be to have the event handler for handleFormData explicitly announce the next event in the sequence and pass on the event arguments so that any changes to the event object are passed on. If handleFormData is only announced from one event handler, you can do this:

<event-handler event="processForm" access="public">
	<announce event="handleFormData" />
</event-handler>

<event-handler event="handleFormData" access="private">
	.. handle the form data ..
	<announce event="displayResult" />
</event-handler>

In real-world applications, it is likely that handleFormData is announced from more than one event handler and that displayResult is not always the next event to announce. Because of that, you need to dynamically select the next event to announce. There are several ways to do this but the cleanest solution is probably to use <event-arg> to store the next event name and then an event filter to announce that - this technique is sometimes called continuation:

<event-handler event="processForm" access="public">
	<event-arg name="continuationEvent" value="displayResult" />
	<announce event="handleFormData" />
</event-handler>

<event-handler event="handleFormData" access="private">
	.. handle the form data ..
	<filter name="continuation" />
</event-handler>

The continuation filter simply announces the event specified by continuationEvent:

<cfset arguments.eventContext.announceEvent(
			arguments.event.getArg("continuationEvent"),
			arguments.event.getArgs() ) />

Exception Handling

When an exception occurs in a filter or listener method, the framework catches the exception, creates a MachII.util.Exception object containing the details of the exception and then announces the exception event, as defined in the <properties> section in the mach-ii.xml file. You can use The handleException() Plugin Point to provide some custom processing when an exception occurs. You can also use <event-mapping> to cause a specific event to be triggered when an exception occurs:

<event-handler event="someEvent" access="public">
	<event-mapping event="defaultException" mapping="someFilterException" />
	<filter name="someFilter" />
	<event-mapping event="defaultException" mapping="someListenerException" />
	<notify listener="someListener" method="someMethod" />
</event-handler>

In this example, it is assumed that defaultException is the event defined by the exceptionEvent property in the <properties> section of the mach-ii.xml file. If an exception occurs in someFilter, the event someFilterException will be announced by the framework. If an exception occurs in someListener.someMethod(), the event someListenerException will be announced by the framework. If no event mapping is active, the defaultException event will be announced when an exception occurs. An event mapping is active from the point it is declared to the end of that event handler.

Designing Event Filters For Mach II

This section looks at designing and using event-filters. The first issue to consider is whether you want an event filter or a plugin. Having decided that you want an event filter, this section looks at some practical reasons for using them to improve the structure and flow of your application and then examines larger issues such as form handling, validation and security.

Event Filter or Plugin?

When you first start using Mach II, it can be difficult to tell whether you need an event filter or a plugin. You need to ask yourself a few questions about what you are trying to achieve so you can determine which will work better for the problem you are trying to solve:

If you answered "yes" to any of those questions, you probably need a plugin.

If you answered "yes" to any of those questions, you probably need an event filter.

If you answered "no" to all of the above but still think you need an event filter or a plugin, then read through the rest of this section and the next section, on plugins, and decide for yourself.

Anatomy Of An Event Filter

An event filter is a component that provides a filterEvent() method which is called with three arguments:

  1. the current event
  2. the current eventContext
  3. (optional) a struct of name/value pairs from the <parameter> tags in the filter invocation

filterEvent can either return true, in which case processing of the event handler continues, or it can return false, in which case processing of the current event handler is terminated (and processing continues with the next event in the queue - unless you clear the event queue by calling arguments.eventContext.clearEventQueue() in the filter). Usually, when a filter returns false, it will also announce a new event before doing so (if it clears the event queue, it must announce a new event after doing so!).

An event filter can define a configure() method if it needs to perform initialization - this is called automatically by the framework. Event filters - like all other parts of the Mach II framework - are stored in application scope so their instance variables are effectively application scope variables.

A minimal event filter CFC looks like this:

<cfcomponent extends="MachII.framework.EventFilter">
	<cffunction name="configure" returntype="void" access="public" output="false">
		<!--- perform any initialization --->
	</cffunction>
	<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
		<cfargument name="event" type="MachII.framework.Event" required="yes" />
		<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
		<cfargument name="paramArgs" type="struct" required="yes" />
		<!--- return false if you need to abort the current event --->
		<cfreturn true />	<!--- indicate success --->
	</cffunction>
</cfcomponent>

The filter is declared inside the <event-filters> .. </event-filters> tags in mach-ii.xml as follows:

<event-filter name="filterName" type="Path.To.YourFilter" />

where filterName is the name you want to give the filter within the mach-ii.xml file and Path.To.YourFilter is the fully-qualified component name for YourFilter.cfc. You may optionally specify parameters for the filter declaration:

<event-filter name="filterName" type="Path.To.YourFilter">
	<parameters>
		<parameter name="param1" value="value1" />
		<parameter name="param2" value="value2" />
	</parameters>
</event-filter>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the filter using the getParameter() method - see below.

The filter is used in an event handler as follows:

<filter name="filterName" />

This causes the filterEvent() method to be called with the current event, the current event context and an empty paramArgs struct.

You may optionally specify parameters for the filter usage:

<filter name="filterName">
	<parameter name="param1" value="newValue1" />
	<parameter name="param3" value="value3" />
</filter>

This causes the filterEvent() method to be called with the current event, the current event context and a paramArgs struct containing two keys: param1 with a value of newValue1 and param3 with a value of value3. The intent is that parameter values passed in this way would override any default parameter value specified in the declaration of the filter. You would typically manage this with code that looked like this:

<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
	<cfargument name="event" type="MachII.framework.Event" required="yes" />
	<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
	<cfargument name="paramArgs" type="struct" required="yes" />
	<cfset var param1 = getParameter("param1") />
	<cfset var param2 = getParameter("param2") />
	<cfset var param3 = getParameter("param3") />
	<!--- other var declarations --->
	<cfif structKeyExists(paramArgs,"param1")>
		<cfset param1 = paramArgs.param1 />
	</cfif>
	<cfif structKeyExists(paramArgs,"param2")>
		<cfset param2 = paramArgs.param2 />
	</cfif>
	<cfif structKeyExists(paramArgs,"param3")>
		<cfset param3 = paramArgs.param3 />
	</cfif>
	<!--- perform filter processing --->
	<!--- return false if you need to abort the current event --->
	<cfreturn true />	<!--- indicate success --->
</cffunction>

An event filter has access to both the current event and the current event context so it is able to affect the logical flow of an application by manipulating the event context. For example, when an event filter decides to abort the current event (by returning false), it generally needs to announce another event for the framework to execute and it may also decide to clear any events that are queued up for execution. By convention, event filters that do that usually have a parameter that specifies the event to announce as well as a parameter that specifies whether or not to clear the event queue:

<cffunction name="filterEvent" returntype="boolean" access="public" output="false">
	<cfargument name="event" type="MachII.framework.Event" required="yes" />
	<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
	<cfargument name="paramArgs" type="struct" required="yes" />
	<cfset var invalidEvent = getParameter("invalidEvent") />
	<cfset var clearEventQueue = getParameter("clearEventQueue") />
	<!--- other var declarations --->
	<cfif structKeyExists(paramArgs,"invalidEvent")>
		<cfset invalidEvent = paramArgs.invalidEvent />
	</cfif>
	<cfif structKeyExists(paramArgs,"clearEventQueue")>
		<cfset clearEventQueue = paramArgs.clearEventQueue />
	</cfif>
	<cfif someCondition>
		<!--- note: clearEventQueue parameter is really a string --->
		<cfif clearEventQueue is "true">
			<cfset arguments.eventContext.clearEventQueue() />
		</cfif>
		<!--- pass current event's arguments into the new event: --->
		<cfset arguments.eventContext.announceEvent(invalidEvent,arguments.event.getArgs()) />
		<cfreturn false />
	</cfif>
	<cfreturn true />
</cffunction>

Data Validation

When user data enters your application, either as URL scope or form scope variables, you generally need to perform some sort of data validation. This may be as simple as checking that certain variables have been provided or it may be something substantially more complex.

The Mach II framework provides the RequiredFieldsFilter event filter to allow you to check that a specified list of URL or form scope variables are all present, announcing a specified event if one or more is missing and returning false to abort the current event. This filter takes two parameters:

If any fields are missing, the event that is announced has the same arguments as the current event plus the following additional arguments:

For more complex web form handling, you should create a bean to encapsulate the web form data (and use the <event-bean> command to create the bean object from the event arguments) and provide a validate() method on that data object that returns a boolean. Then you can use the ValidateFormObject filter (from corfield.org) to invoke that method and handle any failure. This filter takes two or three parameters:

If validate() returns false, the filter also returns false after optionally clearing the event queue and then announcing the specified event with the same arguments as the current event plus the following additional argument:

It is up to the specified event handler how to deal with reporting the validation failure, which is likely to involve additional calls to the data object and, therefore, additional event filter invocations to manage that interaction.

You can specify default parameters for the ValidateFormObject event filter:

<event-filter name="barValidator" type="MachII.filters.ValidateFormObject">
	<parameters>
		<parameter name="formObjectName" value="bar" />
		<parameter name="invalidEvent" value="formHasInvalidBar" />
	</parameters>
</event-filter>
<event-filter name="fooValidator" type="MachII.filters.ValidateFormObject">
	<parameters>
		<parameter name="formObjectName" value="foo" />
		<parameter name="invalidEvent" value="formHasInvalidFoo" />
		<parameter name="clearEventQueue" value="true" />
	</parameters>
</event-filter>

You can now use these event filters without needing to specify the parameters each time or you can override the defaults:

<event-bean name="bar" type="my.model.bar" />
<filter name="barValidator" />
...
<event-bean name="foo" type="my.model.foo" />
<filter name="fooValidator">
	<parameter name="invalidEvent" value="warnAboutBadFoo" />
	<parameter name="clearEventQueue" value="false" />
</filter>

Security

When user authorization needs to be performed on certain events, an event filter is probably the simplest way to achieve this. The Mach II framework provides the PermissionsFilter event filter to support simple permission-based authorization checking. This filter checks that the current user has all of the necessary permissions (specified as a comma-separated list) and if they do not, it announces the event specified (after optionally clearing the event queue). See the comments in the source code for more detail.

You may be able to simply extend the PermissionsFilter component to implement your own security, overriding the getUserPermissions() method to return a comma-separated list of user permissions. If you don't want the default inclusive permission checking behavior, or your user permissions are more complex than a comma-separated list, you can also override validatePermissions() and implement your own checking.

If your security system is more complex than simple permissions, you will need to write your own security event filter.

Designing Plugins For Mach II

This section looks at designing and using plugins.

Anatomy Of A Plugin

A plugin is a component that provides methods which are called with the current eventContext (an object of type MachII.framework.EventContext) at various points in the lifecycle of a request:

  1. preProcess() - called at the start of each request: the eventContext has the current event in the event queue but getCurrentEvent() will not yet return that event (see Processing The First Event In A Request below).
  2. preEvent() - called for each event, immediately prior to execution of the relevant event-handler tag: the eventContext contains the current event (the one that is about to be handled).
  3. preView() - called for each view, immediately prior to execution of the relevant view-page tag: the eventContext contains the current event (the one that is being executed).
  4. postView() - called for each view, immediately after execution of the relevant view-page tag: the eventContext contains the current event (the one that is being executed).
  5. postEvent() - called for each event, immediately after execution of the relevant event-handler tag: the eventContext contains the current event (the one that has just been handled).
  6. postProcess() - called at the end of each request: the eventContext no longer contains an event.
  7. handleException() - called whenever an exception is thrown back to the framework: the eventContext contains the current event (if there is one). This plugin method is also passed the exception object (of type MachII.util.Exception).

A plugin can define a configure() method if it needs to perform initialization - this is called automatically by the framework. Plugins - like all other parts of the Mach II framework - are stored in application scope so their instance variables are effectively application scope variables.

A minimal plugin CFC might look like this:

<cfcomponent extends="MachII.framework.Plugin">
	<cffunction name="configure" returntype="void" access="public" output="false">
		<!--- perform any initialization --->
	</cffunction>
	<cffunction name="preEvent" returntype="void" access="public" output="false">
		<cfargument name="eventContext" type="MachII.framework.EventContext" required="yes" />
		<!--- perform processing prior to every event being handled --->
	</cffunction>
</cfcomponent>

This plugin overrides just the preEvent() method but it could override any of the seven of the methods listed above.

The plugin is declared inside the <plugins> .. </plugins> tags in mach-ii.xml as follows:

<plugin name="pluginName" type="Path.To.YourPlugin" />

You may optionally specify parameters for the plugin declaration:

<plugin name="pluginName" type="Path.To.YourPlugin">
	<parameters>
		<parameter name="param1" value="value1" />
		<parameter name="param2" value="value2" />
	</parameters>
</plugin>

This provides default parameter values for param1 and param2 (of value1 and value2 respectively). These can be accessed within the plugin using the getParameter() method, e.g., getParameter("param1").

The handleException() Plugin Point

You can let the framework handle exceptions by using the exception event and using a single event handler. For many applications this will be perfectly acceptable. You can also add custom exception handling to an application by implementing the handleException() method on a plugin. This plugin point is executed after an exception is encountered, before the exception event is announced and handled. After executing this plugin point, the event queue is cleared and the specified exceptionEvent (from the <properties> section of mach-ii.xml) is announced and handled.

You should not throw an exception from handleException() nor call abortEvent() - both operations will lead to an unhandled exception that will be displayed to the end user. You cannot announce a new event (since the event queue is cleared after handleException() has been executed). You can add information to the current event object which can then be retrieved in the exception event handler:

<cfif arguments.eventContext.hasCurrentEvent()>
	<cfset arguments.eventContext.getCurrentEvent().setArg("argName",argValue) />
</cfif>

This will set the event argument argName to the value argValue if an event was defined when the exception was encountered.

Inside the exception event handler, the current event is the exception event itself which has the following arguments:

You can use <event-mapping> to provide finer-grained control over Exception Handling in event handlers.

Processing The First Event In A Request

Since the "current event" is not set by the time preProcess() is invoked, it is might not be obvious how to process just the first event in each request:

<cffunction name="preProcess" returntype="void">
<cfargument name="eventContext" type="MachII.framework.EventContext" /> <!--- peek at the first event in the queue: --->
<cfset var firstEvent = arguments.eventContext.getNextEvent() /> <!--- ...process firstEvent here... ---> </cffunction>

Note: prior to 1.0.9, the above call to getNextEvent() would remove the first event from the queue!

Revision History

Date
Author
Description

3/7/2005
-
4/14/2005

Sean A Corfield

Release 1.2.1: Contents: no change; Concepts: fixed typos, clarified expandPath() note, some minor formatting changes; Structure: no change; Models: added an example of a custom invoker (Hugo Ahlenius), added example to clarify caching vs. local variables, added partial example for Session Façade, some minor formatting changes; Views: clarified event object usage in views throughout this section, added reference to custom EventInvoker; Event Handlers: no change; Event Filters: added clarification about filter declarations (Rachel Unck); Plugins: added clarification about plugin declarations (Rachel Unck).

3/10/2004
-
12/25/2004

Sean A Corfield

Release 1.2: Contents: updated to 1.0.10 core files, added mach-ii.info as a reference site; Concepts: added note that listeners straddle the line between Controller and Model, clarified that Mach II itself recognizes request.MachIIConfigMode as a default (if present); Structure: added suggestions for keeping listeners separate from the business model (or at least easily identifiable); Models: fixed typos in example code ("cfJoe" and Dave Knapik); tried to clarify the queuing nature of events; elaborated on invokers and type safety; rewrote confusing DAO/memento paragraph and commented out example implementation of DAO/gateway; further clarified transfer objects and their use; added specific reference to the mach-ii.info sample application; Views: recommended unqualified event over request.event in views; added section on paramterized views and the concept of exit events (from Fusebox), added recommendations for using append=; Event Handlers: added verb.noun as an acceptable alternative for event names, as well as additional partitioning event names; Event Filters: fixed typos in example code (Hugo Ahlenius, Tracy Logan); Plugins: no change.

2/4/2004
-
2/16/2004

Sean A Corfield

Release 1.1.1: Contents: updated to 1.0.9 core files; Concepts: no change; Structure: no change; Models: fixed typo in <notify> syntax example (Tracy Logan); Views: no change; Event Handlers: added Exception Handling section; Event Filters: no change; Plugins: noted that handleException() is passed the exception object as well as the event context; expanded Anatomy... section to provide example code for a plugin (Hugo Ahlenius); completely rewrote the Exception Handling section (and renamed it to The handleException() Plugin Point); simplified Processing The First Event In A Request section, now that getNextEvent() is no longer destructive.

1/22/2004
-
1/23/2004

Sean A Corfield

Release 1.1: Contents: added reference links for design patterns; Concepts: updated to 1.0.8 core files; added note about mach-ii.xml security; Structure: no change; Models: added Anatomy... section with a full tutorial on writing a listener, including code examples; recommended CFCInvoker_Event for all listeners and removed workaround for mixing invoker types (bad practice!); added Beans & Form Handling section as an overview on <event-bean> and bean usage; updated article.cfc for <event-bean> and added mach-ii.xml fragment for that example; noted that example code takes some shortcuts with the Memento design pattern; Views: rewrote recommendations about view/controller API to emphasize the current event object and play down request scope; removed references to EventToRequest filter; added reference to Composite View design pattern; Event Handlers: added Decomposing and Chaining Event Handlers section; added code example for event mappings; Event Filters: expanded Anatomy... section to provide a full tutorial on writing a filter, including code examples; added notes about clearing the event queue; removed Request / Event Data Transfer section; removed Beaner Filter section; added <event-bean> usage to Data Validation section; Plugins: expanded explanation of parameter handling.

10/27/2003
-
10/31/2003

Sean A Corfield

Release 1.0.3: Contents: reflect 1.0.7 core files; Concepts: updated to reflect 1.0.7 core files (common mach-ii.cfm; dynamic mode default); added reloadApp suggestion; Structure: no change; Models: no change; Views: dropped mention of RequestToEvent filter; Event Handlers: added note about chaining listener calls using <event-arg> (new in 1.0.7 core files); Event Filters: corrected examples; removed Chaining Listener Calls section; updated Request / Event Data Transfer section in light of changes to <event-arg>; added <event-bean> as an alternative to FormObjectBeaner; Plugins: added handleException() section.

10/9/2003
-
10/17/2003

Sean A Corfield

Release 1.0.2: Corrected some typos (S Page); Expanded Data Access Objects section to better explain DAO, gateway and memento machinery - and added example code (various requesters); Fixed wording in explanation of Implicit Invocation Architecture (Leo Schuman); Made forward / back references into hyperlinks (various requesters); Revisited recommendations about views accessing the current event; Split document into multiple sections (WTG); Fixed several internal links (WTG); Corrected event filter examples (WTG).

10/8/2003

Sean A Corfield

Release 1.0.1: Corrected wording in event filter and plugin sections regarding <parameters> tag block (Jon Gunnip); Corrected various typos and clarified the wording of a few rules (Jon Gunnip, Mark Stanton); Corrected plugin first event examples (Mark Stanton).

10/8/2003

Sean A Corfield

Release 1.0: Initial public release.

 

Send me an e-mail when comments are added to this page | Comment Report

Current page: http://livedocs.adobe.com/wtg/public/machiidevguide/printable.html