Mach-II Primer : 5 : Mediating events with Listeners
Queries have been moved out of the View, but now object instantiation has been moved in. Using Implicit Invocation Listeners, we'll remove object instantiation from the View and reference objects and methods from the Controller (mach-ii.xml).
Job Descriptions
Last step we stuck contacts.cfm with the job of creating the ContactGateway.cfc object, but that's not in its job description.
<cfset qContacts = contactGateway.getAllContacts() />
<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
<ul>
<cfoutput query="qContacts">
<li>
[ <a href="contact_form.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">Edit</a> ]
<a href="contact_detail.cfm?CONTACT_ID=#qContacts.CONTACT_ID#">#qContacts.CONTACT_LAST_NAME#, #qContacts.CONTACT_FIRST_NAME#</a>
</li>
</cfoutput>
</ul>
The View's job is to display data and content, not to get data or connect to objects that get data. What we need here is some kind of mediator to connect the Model to the View.
The Mach-II Listener Object
A Listener is an Object (CFC) that connects the Model to the Mach-II framework.
Heavy lifting is not in the Listener's job description. Small bits of conditional logic is one thing, but large chunks of business logic should be done in the Model and only managed by the Listener.
Now then, repeat after me:
The Model is independant from the framework.
The Model is independant from the framework.
The Model is independant from the framework.
A website can use a single Model and share it with multiple applications. Those applications can be built with or without Mach-II. The applications built with Mach-II use Listeners to connect to the Model. The applications built without Mach-II use other means to connect to the Model.
Create the Listener
Here is ContactListener.cfc which contains two methods:
<cfcomponent name="ContactListener" displayname="ContactListener" output="false" extends="MachII.framework.Listener" hint="ContactListener for CF Contact Manager Demo">
<cffunction name="configure" access="public" output="false" returntype="void" hint="Configures this listener as part of the Mach-II framework">
<!--- do nothing for now --->
</cffunction>
<cffunction name="getContactList" access="public" output="false" returntype="query" hint="returns a query recordset from the ContactGateway">
<cfset var contactGateway = createObject("component", "ContactGateway").init( DSN = request.DSN ) />
<cfreturn contactGateway.getAllContacts() />
</cffunction>
</cfcomponent>
configure()
This is the constructor method of the Listener (just like init() in a normal CFC). When Mach-II loads the configuration file (mach-ii.xml), it will find any Listener definitions and run their configure() method to initialize the Listener and load it into the framework.
getContactList()
This instantiates (creates) the ContactGateway object, calls the getAllContacts() method and returns the recordset to the event object. Note that its returntype is the same as getAllContacts() since it is returning the query and not the Gateway object.
Extending Components
One very important thing to note is that any Listener you create has to extend the base Mach-II Listener.
This means that ContactListener.cfc can directly call any method defined in it PLUS any method defined in /MachII/framework/Listener.cfc.
In other words, when componentA.cfc extends componentB.cfc, componentA.cfc inherits all the methods (functions) in componentB.cfc.
Defining a Listener
Listeners have thier own section of the mach-ii.xml file.
<listeners>
<listener name="ContactListener" type="mach-ii-primer.m2.model.contacts.03.ContactListener" />
</listeners>
Simple, huh?
Notifying an Event to use a Listener
Now we need to update the event-handler for showContacts in mach-ii.xml. Using the <notify> tag, we can tell the event what method in which defined Listener to call.
We can optionally create an event argument to store the results of that method in the same tag. As discussed already, once the results have been put into an eventArg, they are available to any subsequently defined process or object in the event.
<event-arg name="pageTitle" value="Contact List" />
<!-- Use 'notify' to connect the Model to the View through the event object -->
<notify listener="ContactListener" method="getContactList" resultArg="qContacts" />
<view-page name="header" />
<view-page name="lhsMenu" contentArg="sidebar" />
<view-page name="contactList" contentArg="mainContent" />
<view-page name="template" />
<view-page name="footer" />
</event-handler>
Displaying the record set in the View
Finally, update contacts.cfm to reference the event argument "qContacts".
<cfset contactList = event.getArg("qContacts") />
<p><a href="contact_form.cfm?CONTACT_ID=0">Add a Contact</a></p>
<ul>
<cfoutput query="contactList">
<li>
[ <a href="contact_form.cfm?CONTACT_ID=#contactList.CONTACT_ID#">Edit</a> ]
<a href="contact_detail.cfm?CONTACT_ID=#contactList.CONTACT_ID#">#contactList.CONTACT_LAST_NAME#, #contactList.CONTACT_FIRST_NAME#</a>
</li>
</cfoutput>
</ul>
The Return on Investment (ROI)
At this point, you're probably thinking "what good does all this do? I've got two more files than I started with and now I have to look in at least three different places to find my code."
You may not believe this yet, but you're about to cut down on a lot of coding in the future.
Let me explain.
No, there is too much. Let me sum up.
- Queries have been removed from the presentation layer.
- The code in the presentation layer is drastically reduced
- The Gateway object manages an often used query.
- The Mach-II Listener object manages the Gateway object
- The framework was connected to the Model using one line of XML.
You can see these changes in action by using the version 3 files.
- Rename the version 2 mach-ii.xml file to mach-ii.02.xml
- Rename mach-ii.03.xml to mach-ii.xml
- Reload the page (event) in your browser
Next Step
Now that we can list multiple records with Mach-II, how do we manage just one? Using Beans and Data Access Objects (DAOs), we'll throw some CRUD into the application.









*flees*
They are finally on the way. I've been working on a generic OO + CF Primer that will be referenced through the rest of the Mach-II Primer.
In your Listener, you will create a secondary constructor function named "configure" which lets you define any process that needs to happen when it is created. When the framework initializes your Listener, the ListenerManager will call your "configure()" method to create and cache the object.
Therefore, the ContactListener contains the constructor "init()" by means of inheritance, but you do not define "init()", you define "configure()".