Release 1.2.1 (4/14/2005)
« Designing Views | Contents | Designing Event Filters »
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" />
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.
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).
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.
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() ) />
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 Views | Contents | Designing Event Filters »
Send me an e-mail when comments are added to this page | Comment Report
Current page: http://livedocs.adobe.com/wtg/public/machiidevguide/eventhandlers.html