Primer Part 5: Mediating Events with Listeners - Mach-II/Mach-II-Framework GitHub Wiki

Table of Contents

  1. Job Descriptions
  2. The Mach-II Listener Object
  3. Create the Listener
  4. Extending Components
  5. The Return on Investment (ROI)
  6. Other Chapters

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 contactGateway = createObject("component", "m2.model.contacts.03.ContactGateway").init( DSN = request.DSN ) />
    <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.

    <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>

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 independent from the framework. The Model is independent from the framework. The Model is independent 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.

CreateTheListenerCreate the Listener

Here is ContactListener.cfc which contains two methods:

    <!--- /m2/model/contacts/03/ContactListener.cfc --->
    <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.

    <cfcomponent name="ContactListener" displayname="ContactListener"
        output="false" extends="MachII.framework.Listener"
        hint="ContactListener for CF Contact Manager Demo">

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.

    <!-- /m2/config/mach-ii.xml -->
    <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 thGe 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-handler event="showContacts" access="public">
        <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".

    <!--- /m2/views/contacts/03/contacts.cfm --->
    <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.

  1. Queries have been removed from the presentation layer.
  2. The code in the presentation layer is drastically reduced
  3. The Gateway object manages an often used query.
  4. The Mach-II Listener object manages the Gateway object
  5. 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.

  1. Rename the version 2 mach-ii.xml file to mach-ii.02.xml
  2. Rename mach-ii.03.xml to mach-ii.xml
  3. Reload the page (event) in your browser

Other Chapters

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.

Special thanks to Adrian J. Moreno of IKnowKungFoo for contributing this series of primers.

⚠️ **GitHub.com Fallback** ⚠️