Feb 7 2007

Stupid Cairngorm Trick: "View Listeners"

Posted by Joe Rinehart at 7:38 AM
10 comments
- Categories: Flex | Cairngorm

I like Cairngorm. I know some folks see it and say "whoa...that's a lot of classes for a single gesture. Events, Commands, Controllers, Delegates, Locators...aieee." However, "everything in its place" mentality and separation of concerns it enforces really appeal to me.

However, one thing I absolutely can't stand is the ViewLocator/ViewHelper bit. The design forces causing it (separation of controller from knowledge of currently existing views and their implementation) are natural, and require a solution. I'm just not sure ViewHelper/ViewLocator are the right answers. They leave me writing code that still knows about the ID strings with which ViewHelpers have registered themselves.

Let's wrap a use case around this: I have a UserLoginEvent mapped to UserLoginCommand. When the UserLoginCommand gets its response from the backend server, I've got some view behavior that I'd like to take place that goes beyond what binding to the ModelLocator can provide. In other words, I need to call a function named loginFailed() on the view. Using ViewHelper/ViewLocator, I can add a ViewHelper to the view that exposes a loginFailed() function that the command will call: its purpose is to call the actual loginFailed() function on the view, disconnected the view's implementation from the command/controller layer. Complicated.

I started looking through the core Cairngorm code last night, and I noticed that the CairngormEventDispatcher's addEventListener() message just wants two parameters: the name of the event listened for and a reference to a listener function.

In Model-Glue terms:

<message-listener name="someMessage" function="someFunction" />

In Cairngorm, as in Model-Glue, the function referenced is normally on a controller.

However, unlike Model-Glue (and HTML apps in general), Flex/Flash gives us the ability to have views respond and change their display without reloading the page. This means there's no technical reason why the listener function couldn't be on a view.

By adding functions acting as intialized() and removed() handlers, you can add code to your view that listens for events, just like the controller:

private function addViewListeners():void {
   CairngormEventDispatcher.getInstance().addEventListener(AuthenticationController.USER_LOGIN_RESULT, loginResults);
}

private function removeViewListeners():void {
   CairngormEventDispatcher.getInstance().removeEventListener(AuthenticationController.USER_LOGIN_RESULT, loginResults);
}

Now, through a sequence of events, I can have the UserLoginEvent's handling code fire a UserLoginResult event. When it's dispatched, my view will "hear" it and fire its loginResults function. Simple.

Conclusion

The CairngormEventDispatcher allows for functions that aren't just methods on the controller to be added as event listeners. By taking advantage of this, we can allow views to respond to architectural events as well as controllers.

It's not without problems: our view is suddenly bound to Cairngorm's event architecture. I'm also not sure how much this stretches MVC...the model's still completely separate, which is my largest concern. It's just that the views are taking on some knowledge of the controller tier (names of events, existence of certain controllers used to get those names, the dispatcher itself) that I'm not sure they should have.

Thoughts?

Comments

mike

mike wrote on 02/07/07 9:19 AM

would you mind posting some working code? thanks
Spike

Spike wrote on 02/07/07 11:50 AM

I ran into the same issue a while ago and have taken a slightly different approach.

I created a an event (CalBackEvent) that extends CaringormEvent and adds an optional callback function. Each command has a property called completionListener typed as Function. In command.execute I check if the event is of type CallBackEvent. If so, completionListener is assigned. When command.result() is fired, completionListener is called. In order to provide a framework to give some meaningful information to the code inside the completion listener I added a ViewAdvisor layer that inspects the result from the server, and possibly the model, and creates an instance of a ViewAdvice class. This class acts as a Transfer Object for information that I want to pass to the view.

Within the view layer, responsibility for view control goes from parent to child. Each parent is completely responsible for the lifecycle of its children.

So far it's working out pretty well.
bokel

bokel wrote on 02/07/07 12:41 PM

Changing a value in the model &quot;can&quot; trigger a function. One way is to use a setter another way is to use Adobe Consultings Observe Tag.
Spike

Spike wrote on 02/07/07 12:46 PM

Yeah, I did have everything in the model and was using the Observe tag for a while, but it made the model very unwieldy, and I found that putting view state information in the model coupled the model very heavily to the view. That doesn't feel right to me which is why I prefer my current approach.
Dave Carabetta

Dave Carabetta wrote on 02/07/07 2:32 PM

Hey Joe. First, I think it's important to note that even the Cairngorm group discourages the use ViewHelper/ViewLocator pattern in CG 2.1 in favor of using Flex's built-in binding architecture. Second, I've always used your proposed approach in my Flex 2 applications, as it seems to make the most sense. In my mind, Flex is an event broadcasting framework by its very nature, so the idea of mimicking that pattern to broadcast and listen for custom events for my application is a natural one.

Regarding your concerns about coupling, I'm on the fence. If I'm building truly reusable widgets for my overall application, I don't use Cairngorm so that I can easily drop it in to any application. I like to keep those framework-agnostic for obvious reasons. However, the coupling of Cairngorm is not just limited to your views...your events, commands, delegates, etc., all do as well. I don't mind this because Cairngorm brings along with it the separation you talked about and other great features that keep my codebase structured, organized, and &quot;clean.&quot; Yes, my views &quot;know&quot; about Cairngorm, but so does the rest of my application.

Just my $.02
Jay Proulx

Jay Proulx wrote on 02/08/07 12:14 PM

I implemented the same thing, albeit slightly differently. I created a &quot;ViewController&quot; class, which was almost a complete copy and paste of the FrontController. ViewController extends Canvas, Commands were replaced with strings representing a particular state, and you create a new state for every &quot;primary view&quot; for the View Controller you're working with. In the constructor for your ViewController you run &quot;addView&quot; as many times as necessary to register your events with different states.

Dispatch a CairngormEvent, your ViewController is listening, and switches states. Verrrry simple. You can also have your FrontController and ViewController listening to the same events so that logic runs at the same time as changing views, also very handy.

Since a ViewController is typically implemented as an MXML component, you can create as many of them as you need for your application. You can nest them, and use them any time you want a CairngormEvent to represent a change in views.

So far, I haven't found a downside to it, it's completely encapsulated and abstracted, and there is _NO_ logic/view co-mingling.

I've already contacted the cairngorm-devel guys and I'm trying to get it added to the core cairngorm release.
Phil

Phil wrote on 08/11/07 6:57 PM

Like mike i'd also like some sample code for this. I'm trying to apply this in my own application but its not working for me. The event listener isn't listening for the event. Does anyone know of any tutorials out there? thanks.
Maverick

Maverick wrote on 03/14/08 8:25 AM

I agree with you on this one. When I started looking at the Cairngorm architecture, ViewHelper/ViewLocator seem to be out of place. In the most recent version, they have actually depricated the classes.

Now coming to solve the problem you narrated, I agree that using &quot;events&quot; makes sense as that also falls in the &quot;Flex-programming paradigm&quot;. But, how do we allow users to add events is the key.

One of the most important part is to find a way that is seam-less. I am not sure, if you have seen the &quot;Command-Responder&quot; pattern. I would like to do something like that. I would have seen Cairngorm implement that with the View and Command; where the view passes an object to the Command and then in the command you add to the view to the responder. Which means once when the Command will raise the event it will automatically get caught - but till it does not comes, using the approach you outlined is great.
Mark Campion

Mark Campion wrote on 04/01/08 5:56 AM

I'm in a similar situation. I have implemented the command patternt for events fired from the view to the controller. Using the idea of a front controller (the listener) this delegates control to a command object. However, I then want the command object to dispatch an event, that another listener will catch and then update the view. However, my events do not seem to be getting caught. Any ideas where they are bubbling to? How can I catch them?

Write your comment



(it will not be displayed)