Mach II : Anatomy of "ContactManager"

The ContactManager is a fairly simple example application that shows some of the power of Mach II. We'll take it apart, piece by piece and explain how it all works.

Structure

First of all, we'll look at the overall (directory) structure of the application:

mach-ii.xml

The Mach II website has some documentation on Configuring Mach II that explains some of the XML configuration file. The Mach II DTD also contains information about what the XML configuration file means. Next we'll take a look at the configuration file for the ContactManager sample application, line-by-line:

<mach-ii version="1.0">

<!-- PROPERTIES -->
<properties>
<property name="defaultEvent" value="showHome" />
<property name="exceptionEvent" value="exception" />
<property name="applicationRoot" value="/ContactManager" />
<property name="eventParameter" value="event" />
<property name="parameterPrecedence" value="form" />
<property name="maxEvents" value="10" />
</properties>

In the properties section, we define six values:

    <!-- LISTENERS -->
<listeners>
<listener name="ContactManager" type="ContactManager.model.ContactManager">
<invoker type="MachII.framework.invokers.CFCInvoker_EventArgs" />
</listener>
<listener name="RecentContacts" type="ContactManager.model.RecentContacts">
<invoker type="MachII.framework.invokers.CFCInvoker_EventArgs" />
<parameters>
<parameter name="maxRecentContacts" value="3" />
</parameters>
</listener>
</listeners>

This defines two listeners. A listener is a CFC that extends MachII.framework.Listener and which is part of the "model" for the application. Each listener defines how it is invoked - either with an event object (MachII.framework.invokers.CFCInvoker_Event) or with event arguments (MachII.framework.invokers.CFCInvoker_EventArgs). The former passes an event argument to methods on the listener, the latter passes each of the event object's arguments as separate arguments to methods on the listener. Which you use will be a matter of style. The ContactManager listeners expect to get a variety of arguments passed, depending on what data was available to the event itself (form and / or URL data, mostly).

    <!-- EVENT FILTERS -->
<event-filters>
<event-filter name="eventArgs" type="MachII.filters.EventArgsFilter" />
<event-filter name="requiredFields" type="MachII.filters.RequiredFieldsFilter" />
<event-filter name="contactBeaner" type="ContactManager.filters.ContactBeanerFilter" />
</event-filters>

This defines three event filters. Event filters are CFCs that extend MachII.framework.EventFilter and define a filterEvent method. Event filters are passed the current event, the current event context and a sequence of name / value parameters. The specific event filters defined here perform the following tasks:

    <!-- EVENT-HANDLERS -->
<event-handlers>
<event-handler event="showHome" access="public">
<notify listener="RecentContacts" method="getRecentContacts" resultKey="request.recentContacts" />
<view-page name="mainMenu" contentKey="request.content" />
<view-page name="mainTemplate" />
</event-handler>

The event handler for the showHome event - the default event. It is a public event handler so it can handle events generated by users (in links and form actions). This calls getRecentContacts() on the CFC behind the RecentContacts listener (which is ContactManager.model.RecentContacts) and stores the result in request.recentContacts (the resultKey). Then it renders the mainMenu view, which displays request.recentContacts, and stores the output in request.content (the contentKey). Finally it renders the mainTemplate view, which wraps request.content in a layout (and outputs the HTML because no resultKey is specfied).

        <event-handler event="newContact" access="public">
<!-- One way to set event-args... -->
<event-arg name="submitEvent" value="createContact" />
<event-arg name="submitLabel" value="Create" />
<announce event="showContactForm" copyEventArgs="true" />
</event-handler>

The event handler for the newContact event - the "Add Contact" link. This uses the event-arg operation to set two arguments within the event and generates a new event (showContactForm) with the current event's argument copied in - including the two arguments set in this handler. See showContactForm below for how processing continues at this point.

        <event-handler event="editContact" access="public">
<notify listener="ContactManager" method="getContact" resultKey="request.contact" />
<!-- Second way to set event-args... -->
<filter name="eventArgs">
<parameter name="submitEvent" value="updateContact" />
<parameter name="submitLabel" value="Update" />
</filter>
<announce event="showContactForm" copyEventArgs="true" />
</event-handler>

The event handler for the editContact event - the "edit" link when viewing a contact. The link passes id= which is copied into the event object. This handler calls the getContact() method on the CFC behind the ContactManager listener (which is ContactManager.model.ContactManager), passing in the id value, and stores the result - the retrieved contact - in request.contact (the resultKey). Then it uses the eventArgs filter to set two arguments within the event object - an alternative to how newContact above is processed. Again, the showContactForm event is generated to continue processing.

        <event-handler event="showContactForm" access="public">
<filter name="contactBeaner" />
<view-page name="contactForm" contentKey="request.content" />
<view-page name="mainTemplate" />
</event-handler>

The event handler for the showContactForm event - used by both add and edit above. This uses the contactBeaner filter to ensure that a properly constructed Contact object is present in the event object - taken from request.contact in the case of edit or from the event in the case of add (which, clearly, is initially empty). The contactForm view is rendered into request.content which is then presented as HTML by the mainTemplate view. Note that the contents of the submit button and the event passed to the form action are both determined by event arguments set in the event handlers above (submitLabel and submitEvent respectively).

        <event-handler event="createContact" access="public">
<filter name="requiredFields">
<parameter name="requiredFields" value="firstName,lastName,street,city,state,zip" />
<parameter name="invalidEvent" value="newContact" />
</filter>
<notify listener="ContactManager" method="createContact" resultKey="request.createdContact" />
</event-handler>

The event handler for the createContact event - the submitted event for adding a contact. This uses the requiredFields filter to check that certain fields are present (and, if not, generates the newContact event and aborts this handler by returning false - processing continues with the newContact event above but now the event object contains the message and missingFields information as well as the contact information already entered). If all the fields are present, the createContact() method on the ContactManager listener is called and the result stored in request.createdContact. The createContact() method generates a contactCreated event to continue processing - see below.

        <event-handler event="updateContact" access="public">
<filter name="requiredFields">
<parameter name="requiredFields" value="firstName,lastName,street,city,state,zip" />
<parameter name="invalidEvent" value="newContact" />
</filter>
<notify listener="ContactManager" method="updateContact" resultKey="request.createdContact" />
</event-handler>

Very similar to the event handler for createContact above, this handler for updateContact also checks required fields but calls updateContact() on the listener (instead of createContact()) which generated a contactUpdated event to continue processing. Otherwise the behavior is as above.

        <event-handler event="contactCreated" access="private">
<notify listener="RecentContacts" method="addRecentContact" />
<announce event="listContacts" /> </event-handler>

The handler for contactCreated - generated by the createContact() call above - simply calls addRecentContact() and then generates a listContacts event (to cause the contacts to be redisplayed). It is a private event handler so it can only be invoked for events generated by the application itself, not by events generated by the user.

        <event-handler event="contactUpdated" access="private">
<notify listener="RecentContacts" method="addRecentContact" />
<announce event="listContacts" />
</event-handler>

Very similar to the handler for contactCreated above, this handles the contactUpdated event generated by the updateContact() call above.

Generating different events for createContact() and updateContact() allows more flexibility for future enhancements, even though in this simple application it seems to add complexity and duplication. An alternative way to handle these two events - since they currently do the same thing - would be to have both of their handlers simply announce a new event, say, contactTouched, and add a handler for that which adds the contact to the "recent" list and then announces listContacts to redisplay the contacts. If there were additional events in the system that were all handled this same way, this might be a good way to reduce the duplicate calls to addRecentContact().

        <event-handler event="listContacts" access="public">
<notify listener="RecentContacts" method="getRecentContacts" resultKey="request.recentContacts" />
<notify listener="ContactManager" method="getAllContacts" resultKey="request.allContacts" />
<view-page name="contactList" contentKey="request.content" />
<view-page name="mainTemplate" />
</event-handler>

The handler for listContacts calls getRecentContacts() on the RecentContacts listener and then calls getAllContacts() on the ContactManager listener, storing both results in request scope variables for use by the contactList view which it renders into request.content and then finally renders the mainTemplate view as HTML.

        <event-handler event="viewContact" access="public">
<notify listener="ContactManager" method="getContact" resultKey="request.contact" />
<view-poge name="viewContact" contentKey="request.content" />
<view-page name="mainTemplate" />
</event-handler>

The handler for viewContact - the "View Contact" link in the application - which is passed an id (in the URL - it becomes an event argument). The handler retrieves the specified contact, just like the editContact handler did, above, and then renders the viewContact view into a variable and the mainTemplate view to HTML.

        <event-handler event="cancelUpdateContact" access="public">
<announce event="listContacts" />
</event-handler>
<event-handler event="cancelCreateContact" access="public">
<announce event="listContacts" />
</event-handler>

These two handlers are for events generated by pressing "Cancel" on the update and add/create contact forms respectively. They both simply generate the listContacts event to redisplay the main list of contacts.

        <event-handler event="exception" access="private">
<view-page name="exception" contentKey="request.content" />
<view-page name="mainTemplate" />
</event-handler>
</event-handlers>

The final event handler is for the exception event - which is generated if an exception occurs but is not handled by your code and propagates out to the Mach II framework. See the exceptionEvent property at the beginning of the XML file.

    <!-- PAGE-VIEWS -->
<page-views>
<page-view name="mainTemplate" page="/views/mainTemplate.cfm" />
<page-view name="mainMenu" page="/views/mainMenu.cfm" />
<page-view name="contactList" page="/views/listContacts.cfm" />
<page-view name="viewContact" page="/views/viewContact.cfm" />
<page-view name="contactForm" page="/views/contactForm.cfm" />
<page-view name="exception" page="/views/exception.cfm" />
</page-views>

This section defines the various HTML views used by the application. The page attribute paths are relative to the applicationRoot defined in the properties section above.

    <!-- PLUGINS -->
<plugins>
</plugins>

</mach-ii>

There are no plugins in the sample application. I'll write more about plugins later.

More Information...

Read more about...