Dec 13 2006

Cairngorm for MG/ColdFusion Developers (Part 5, Commands and Events)

Posted by Joe Rinehart at 9:30 AM
4 comments
- Categories: Flex and ColdFusion

In part five of this series, we're going to get to some of the real core of Cairngorm: events and commands. I'm going to be drawing parallels to the Model-Glue and Mach-ii frameworks for ColdFusion, but I'll still explain things from scratch as well. At the core of the matter, events and commands implement Implicit Invocation: when a UI gesture is handled, such as clicking a button, that "click handler" has no idea what code the controller ultimately invokes.

Well, that skipped ahead about five paragraphs, but should have "clicked" for the MG/M2 crowd. Let's take it in pieces. Introduction

In the last part of this series, we learned about the Value Object pattern, a way of representing our business entities with wee little bean-like objects that make data binding possible.

Our goals in this part are:

1. Learn what Implicit Invocation means 2. Touch on MVC (I'll be linking to a few other articles on it - I think people who read my blog know I could ramble for a bit on this one!) 3. Learn about the command pattern, and compare it to how Model-Glue and Mach-ii work. 4. Implement our ContactController 5. Add a ContactSelected event and a command to invoke when it's dispatched.

Implicit Invocation?

Ok, there's two ways to do something: do it yourself, or ask someone else. Now, in your day to day life, there's things you know how to do: breathe, make coffee, etc. Those could be said to be within the scope of what you, a normal caffeinated coder, should be doing. However, there's other stuff as well: you may go to the bank, but you don't go behind the counter to actually process your transaction. You simply state that the transaction needs to happen, and the teller performs the actual work, without your knowledge of what they're doing.

Implicit Invocation is much the same way. Let's say that we've got a form to edit a contact. It may have a button labelled "Delete Contact." When we click that button, it'd be really easy to do something like this, explicitly asking a backend service to delete the contact:

<mx:RemoteObject id="contactService" destination="ColdFusion" source="com.firemoss.cg4cf.service.ContactService" />

<mx:Script>
   <![CDATA[
      // Pseudo-coding for effect, not validity       private var contact:Contact;

      private function deleteContact() {
         contactService.deleteContact(contact);
      }

</mx:Script>

<mx:Button label="Delete Contact" click="deleteContact()" />

Seems pretty logical, eh? Click the button, and the contact goes into the ether(net). It's how most Flex examples are written. However, it's extraordinarily weak and brittle.

Let's say there's three ways to delete a contact: this button, through a menu item, and, for kicks, by dragging one or more contacts from a list of contacts to a trashcan icon, because things like that are possible in Flex.

We suddenly have three places that all need to know about and rely on that and its interface. We've just repeated ourselves, duplicating code throughout our application, which means we'll have to know/remember where it is when it's time to change things.

Like right about now: there's a new requirement that states that whenever a contact is deleted, we shouldn't call the service immediately, but instead place the Contact instances in a deletion queue, stored in a mythical Recycle Bin we'll be implementing.

We've also had great success with the project, we've hired a junior developer, and we give them this task. They happily implement it on the form and list, but they don't even know the menu item exists, so we've got a bug. Whoops.

II: A Better Way

Implicit Invocation replaces this brittle system, insuring that you don't repeat yourself. In the context of our Cairngorm application, it means that the UI will no longer explicitly run higher-level logic. Instead, it will sort of state what's happened, such as "ContactDeleted," and let a higher authority figure out exactly what that means, and what code should be run.

II with MVC

In a nutshell, Model-View-Controller (CFDJ Article, Wikipedia entry) states that you'll write three kind of code:

  1. Model. It's your business stuff: the real logic of your application, database interaction, etc.
  2. View. It's the code people look at. MXML, HTML, CFM, etc.
  3. Controller. It takes data from the view and asks the Model to do something with it.

The first rule of MVC: the model never knows about the view.

The second rule of MVC: the model never knows about the view.

MVC doesn't explicitly mean Implicit Invocation. It's perfectly possible to be explicit in MVC: your deleteContact() function would call something like controller.deleteContact(contact), which would then fire off whatever Model-level functions necessary (like deleteContact() on the ContactService).

Model-Glue and Mach-II implement implicit invocation via the concept of a "listener function," with Model-Glue having the strongest separation of the two. In Model-Glue, a page or "event" simply broadcasts what are known as messages, defined in XML.

<event-handler name="deleteContact">
   <broadcasts>
      <message name="HeyWouldSomeonePleaseDeleteThisContact" />
   </broadcasts>
</event-handler>

The event itself has no idea what, if anything, takes place at the Controller level. In turn, Model-Glue's Controllers relate their methods to message names, also via XML:

<controller name="theBigBoss" type="ContactController">
   <message-listener message="HeyWouldSomeonePleaseDeleteThisContact" function="DeleteContact" />
</controller>

Cairngorm works in much the same way, albeit without an XML configuration file. You first write a Controller class, extending FrontController in the framework. You instruct the Controller to "listen" for certain events, firing what's known as a Command. Then, you dispatch events, and the Controller implicitly invokes whatever Commands are associated with that event.

Implementing SelectContact

Let's put this in practice. It's a bit weird, but I'm going to comment our View and Controller code with the equivalent bits from the Model-Glue framework.

We want to implement a command that'll display a popup showing us the currently selected Contact's first name whenever a contact is clicked in the grid. Yes, it'd be easy to do this explicitly, in our ContactGrid.mxml component, but didn't we just learn why this sets a bad precedent?

Here we go.

Cairngorm Controllers

First, we need to create a controller. If you've been coding along with these tutorials, create /com/firemoss/cg4cf/controller. In it, create a class called ContactController.as, containing this code:

/*
Model-Glue equivalent:

<cfcomponent extends="ModelGlue.Core.Controller">
</cfcomponent>
*/

package com.firemoss.cg4cf.controller {

   import com.adobe.cairngorm.control.FrontController;

   public class ContactController extends FrontController {

   }
}

Then, we need to tell our application to create an instance of our controller when it's run. It's somewhat wonky - I've changed to using an .mxml just for configuration of Cairngorm - but we'll do this directly in CG4CF.mxml.

First, add a new namespace to the <mx:Application/> tag:

<mx:Application
xmlns:mx="http://www.adobe.com/2006/mxml"
xmlns:view="com.firemoss.cg4cf.view.*"
layout="absolute"
creationComplete="onCreationComplete()"
   
    xmlns:controller="com.firemoss.cg4cf.controller.*"
>

Then, just under the <mx:Application/> tag, instantiate your controller:

<!--
Model-Glue equivalent:

<controller name="theBigBoss" type="ContactController" />
-->
<controller:ContactController />

Cairngorm Commands

Ok, now for the big difference between Model-Glue/M2 and Cairngorm: the ColdFusion frameworks use listener functions, which are methods of the controller. Cairngorm uses Commands, which are separate classes altogether, for a few very good reasons that we'll eventually get to. For now, just think "Command Class = Listener Function in separate file."

Let's go ahead and write the command that will do a popup when it executes. Create /com/firemoss/cf4cf/controller/command (many people place these in controller, but I find it gets cluttered with Controllers, Commands, and Events living in the same place). Now, create ContactSelectedCommand.as in the new "command" directory, giving it this code:

/*
Model-Glue equivalent is a function in a Controller:

<cffunction name="selectCommand" returntype="void">
<cfargument name="event" type="ModelGlue.Core.Event" />
</cffunction>
*/


package com.firemoss.cg4cf.controller.command {
   import com.adobe.cairngorm.commands.Command;
   import com.adobe.cairngorm.control.CairngormEvent;
   
   import mx.controls.Alert;

   public class ContactSelectedCommand implements Command {

      public function execute(event:CairngormEvent):void {
         
         Alert.show("Hi, I'm ContactSelectedCommand.");
         
      }      
   }

}

It's important to note that this class implements an interface known as Command. Command states that it will have a method named "execute" that returns void and requires an instance of a CairngormEvent. Commands often implement a second interface, Responder, but we'll cover that when we deal with remote services.

Cairngorm Events

In our Command, we expect an "event." This is just like the "arguments.event" everyone's used to dealing with in Model-Glue. It has a member, "data," than contains, well, stuff. This "stuff" could be things like the contact that's been selected.

However, that's not really a good idea: how do you know the event really contains a contact? With Flex, we've got strong typing, so we can enforce rules in a way ColdFusion can't (well, it could, but it'd make Model-Glue and M2 a lot less fun to work with).

In Cairngorm, best practice is to create what I call "typed events." In this case, I'll create a typed event that requires an instance of our Contact Value Object in its constructor (see, it's all coming together!). So, I make /com/firemoss/cg4cf/controller/event, and in it, I create ContactSelectedEvent.as:

/*
Model-Glue Equivalent:

There really isn't one, save subclassing ModelGlue.Core.Event,
but there's no way to control instantiation, so, well, sorry.
*/


package com.firemoss.cg4cf.controller.event {

   import com.adobe.cairngorm.control.CairngormEvent;
   import com.firemoss.cg4cf.model.vo.Contact;

   public class ContactSelectedEvent extends CairngormEvent {

      public var contact:Contact;
   
      public function ContactSelectedEvent(contact:Contact) {
            /*
            Model-Glue Equivalent:
            
            <message name="ContactSelected" />
            */

            super("ContactSelected");
            
            
            // The contact to select             this.contact = contact;
      }
   }
}

It's pretty straightforward: ContactSelectedEvent extends Cairngorm event, so when it's passed to ContactSelectedCommand's execute() method, it'll be seen as type CairngormEvent via polymorphism.

Item to note: Note that we're calling super() with a string. This string is the "name" of the event, which is like the name of a message in Model-Glue. Because we have to type it out, it's prone to error: in the last part of this exercise, we'll learn about a Cairngorm best practice that can save us from typed "ContactSelectd" at the compiler level.

Relating Events to Commands

Now, we need to tell the ContactController to fire the ContactSelectedCommand whenever it learns of a new ContactSelectedEvent. The "Model-Glue Equivalent" comment here should make this really stick for the Model-Glue and M2 crowds. It's so important I've included the M2 equivalent.

In ContactController, replace the constructor method with this code:

public function ContactController() {
   /*
   Model-Glue equivalent:
   
   <message-listener message="ContactSelect" function="SelectContact" />
   
   Mach-ii equivalent:
   
   <notify listener="ContactController" method="SelectContact" resultKey="request.SelectContactResult" />
   
   */


   addCommand("ContactSelected", ContactSelectedCommand);
}

If you try to compile, you'll get an error about ContactSelectedCommand. We need to add an import statement so that the compiler knows where to look:

import com.firemoss.cg4cf.controller.command.*;

Note that we've now got the weakness of the "ContactSelected" string relating events named "ContactSelected" (like the ContactSelectedEvent we made) to the ContactSelectedCommand we created earlier. We'll fix this later.

Dispatching our Event

Well, the time has come to try this all out. Let's modify our ContactGrid.mxml file to create a ContactSelected event when it changes, and dispatch the event. The inline comments explain the code:

<?xml version="1.0" encoding="utf-8"?>
<mx:Canvas xmlns:mx="http://www.adobe.com/2006/mxml" width="400" height="300">

<mx:Script>
<![CDATA[
      import com.firemoss.cg4cf.controller.event.ContactSelectedEvent;
      import com.firemoss.cg4cf.model.vo.Contact;
      import com.firemoss.cg4cf.model.ContactModelLocator;
      
      import com.adobe.cairngorm.control.CairngormEventDispatcher;
      
      private function contactSelected():void {
         /*
         The grid is bound to a collection of Contact Value Objects,
         therefore we cast its selection to a Contact VO
         */

         var contact:Contact = Contact(contactList.selectedItem);
         
         /*
         Then, we create our event.
         */

         var event:ContactSelectedEvent = new ContactSelectedEvent(contact);
         /*
         Last, we tell Cairngorm to handle the event.
         
         Model-Glue / M2 equivalent:
         index.cfm?event=contactSelected
         */
   
         CairngormEventDispatcher.getInstance().dispatchEvent(event);
      }
]]>
</mx:Script>

<mx:DataGrid id="contactList" width="100%" height="100%" dataProvider="{ContactModelLocator.getInstance().contacts}" change="contactSelected()" />

</mx:Canvas>

Once compiled, give it a whirl. You should get a popup when you click a contact. Implicit invocation at work!

If you don't get the popup, make sure you didn't mistype "ContactSelected".

Showing the Contact Name

Let's modify our ContactSelectedCommand to show the Contact's name. Inline comments explain:

/*
Model-Glue equivalent is a function in a Controller:

<cffunction name="selectCommand" returntype="void">
<cfargument name="event" type="ModelGlue.Core.Event" />
</cffunction>
*/


package com.firemoss.cg4cf.controller.command {
   import com.adobe.cairngorm.commands.Command;
   import com.adobe.cairngorm.control.CairngormEvent;
   
   import mx.controls.Alert;
   import com.firemoss.cg4cf.controller.event.ContactSelectedEvent;
   import com.firemoss.cg4cf.model.vo.Contact;

   public class ContactSelectedCommand implements Command {

      public function execute(event:CairngormEvent):void {
         
         /*
            Cast the CairngormEvent to the typed event. If this
            cast fails, something is trying to fire a ContactSelectedEvent
            without meeting its requirements.
         */

         var selectedEvent:ContactSelectedEvent = ContactSelectedEvent(event);
         
         /* Show the contacts name*/
         var selectedContact:Contact = selectedEvent.contact;
         
         Alert.show(selectedContact.firstname + " " + selectedContact.lastname);
         
      }      
   }

}

Fixing the "ContactSelected" event name

We've left a huge opportunity for a hard to track error in typing "ContactSelected" as the event name. What would be really nice is a way to use variables instead of hand-typed strings where we need to use an event name.

Remember static variables? Those variables that belong to a class that didn't work so well in our ModelLocator? Well, they work wonders for us now.

First, we'll create a static constant in the ContactController that equals "ContactSelected". Put this code right above the constructor method, so that line 15-17 look like this:

public static const CONTACT_SELECTED:String = "ContactSelected";
      
public function ContactController() {

Now, wherever we've typed "ContactSelected", we can use this static constant. First, we'll change our addCommand() call in ContactController:

addCommand(ContactController.CONTACT_SELECTED, ContactSelectedCommand);

Note that we use "ContactController.", not "this." Remember, static properties belong to the class, and the constructor is dealing with an instance of that class.

Next, in our event, we'll import the controller:

import com.firemoss.cg4cf.controller.ContactController;

And change our super() call in the constructor:

super(ContactController.CONTACT_SELECTED);

String problem solved! Now, we can't even compile the application without using the right names of events. Lesson: Always use static constants to define event names!

Conclusion

Well, we've learned a bit about MVC, a bit about II, and we've put them in place in our application. Changing the ContactGrid's selection implicitly invokes a command that pops the name of the current contact.

However, we haven't really done much of anything as far as our application is concerned. Next time, though, we'll change that: we're going to use our command to populate a new ContactForm view with the currently selected contact, then write a second command to save that contact's changes.

Comments

darron

darron wrote on 12/13/06 10:41 AM

If you have event names that resemble command name... like &quot;DeleteContact&quot; to &quot;DeleteContactCommand&quot;, then you can write an auto-invoker in the controller to automatically fire off the event name = &quot;Command&quot; command. This will avoid having a lot of addCommand( &quot;DeleteContact&quot;, DeleteContactCommand) calls in your controller (but you'll have to make sure to at least reference the DeleteContactCommand somewhere so it gets compiled into the app).

The downside of this is that it couples event names to command names tighter than the addCommand() approach does (which probably isn't a big deal since this is the common convention anyway). The upside is that once the auto-invoke code is written you most likely never have to touch the controller again as long as you name your commands event_name+&quot;Command&quot;, other than making sure the command class is compiled into the app.

Just throwing that out there ;-)
Joe Rinehart

Joe Rinehart wrote on 12/13/06 12:29 PM

Hey Darron,

Thanks for adding that flavor to it - it's an interesting topic to me, because while it saves some typing, it somewhat removes the II piece of Cairngorm that I really like.

If an entire app is build using &quot;autoinvoke&quot; of like-named commands and events, what's the real difference between doing this and simply instantiating and firing Commands directly from the view tier? (Granted, the Command interface wouldn't let you do it.)
darron

darron wrote on 12/14/06 11:26 AM

Hi Joe,

From the view's perspective, everything is the same. The key difference is basically just the controller's implementation.

If you use the compielr directive -include-classes to automatically build in all of your command classes into the final .swf, and have the controller auto-invoke the command based directly off of the event string, there's no maintenance when adding / removing commands.

Everything is still II, but you get rid of the memory overhead of the mapping of string to class, and can move to just use getDefinitionByName() isntead. The -include-classes means the command definitions will always exist at runtime, so there's no need to modify the controller.

It simplifies maintenance a bit... but again, assumes that the string you're using for the event will invoke the string + &quot;Command&quot; command. Typically that's a fair assumption to make, but might not always be the case.

As always, you're mileage may vary. :-)
Kenny S.

Kenny S. wrote on 12/16/06 9:37 AM

Another thing that could be of interest to large application developers would be a trick I just learned recently with Cairngorm. My biggest issue with the framework was always organization of code and controlling what could end up turning into 100 classes for 50 user gestures. I found that grouping your events into a lesser number of classes by using the built in &quot;type&quot; attribute is useful as well. Although this does not allow for auto-invocation, it is useful nonetheless and conforms to how Flex applications work natively. (See MouseEvent.CLICK vs. MouseEvent.MOUSEDOWN)

In context, consider the DeleteContact example from above. You could also have other contact related events such as UpdateContact and NewContact. What I like to do is group these events to one &quot;master&quot; event class called ContactEvent and then use the static vars approach, that Darron makes use of, like so: ContactEvent.DELETE or ContactEvent.UPDATE.

For a good example of this, see the CairngormStore ( 2.0) which does this very thing in an event called UpdateShoppingCart. It's available just about everywhere for download. :)

And to Darron: GREAT work on this article series. I think it really breaks down walls for those CF developers who sometimes shy away from Flex. Not to mention it makes use of a well thought out framework by our buds at I2/Adobe.

Write your comment



(it will not be displayed)