Feb 9 2007

Adding a "ViewController" into your Cairngorm app

Posted by Joe Rinehart at 8:04 AM
11 comments
- Categories: Flex | Cairngorm

The other day, I posted about adding Cairngorm event listeners directly into your views. Just summarizing it like that, it should already sound like a wonky alternative to using ViewHelper/ViewLocator (which, to me, is even wonkier).

Here's my situation:

I have a login form that I'd like to have to something "special" when login fails, like blink or turn red or play a very loud siren noise. Whatever that is, I can't get there by just binding to a "workflow" value in the ModelLocator: I need to call a function in the view.

Jay Proulx posted a possible solution that created a separate "ViewController" concept, where a class would extend Canvas and define string constants representing various states. It's a nice way to separate the listeners out of the views, but it still doesn't go where I need to be: having functions that play things like effect sequences in response to architectural events.

So, I combined his idea, mine, and the basic UI event system to create my own ViewController class.

Here it is (nothing special):

package com.foo.control

{
   import mx.core.UIComponent
   import com.adobe.cairngorm.control.CairngormEventDispatcher;

   public class ViewController extends UIComponent
   {
    private var dispatcher:CairngormEventDispatcher = CairngormEventDispatcher.getInstance();
   
    protected function addListener(eventName:String, func:Function, useCapture:Boolean = false, priority:int = 0, useWeakReference:Boolean = false):void {
       dispatcher.addEventListener(eventName, func, useCapture, priority, useWeakReference);
    }
   }
}

Nothing fancy. Extends UIComponent and adds an addListener() method that'll add a listener to the CairngormEventDispatcher.

However, I can then extend it with something like LoginViewController. LoginViewController adds a listener for the USER_LOGIN_RESULT_EVENT, and depending on the authentication status of the user involved, raises a plain old UI event:

package com.foo.view.controllers
{
   import com.foo.control.ViewController;
   import com.foo.control.user.AuthenticationController;
   import com.foo.control.user.UserLoginResultEvent;
   import com.foo.model.user.User;
   import flash.events.Event;

   [Event(name="loginFailed",type="flash.events.Event")]
   [Event(name="loginSuccessful",type="flash.events.Event")]
   
   public class LoginViewController extends ViewController
   {
      public function LoginViewController() {
         addListener(AuthenticationController.USER_LOGIN_RESULT_EVENT, loginResult);
      }
      
      private function loginResult(result:UserLoginResultEvent):void {
         var u:User = result.user;
         var e:Event;
         
         e = u.authenticated ? new Event("loginSuccessful") : new Event("loginFailed");
         dispatchEvent(e);
      }
   }
}

Now I'm in business. Instead of dealing with views by an arbitrary ID (my main issue with ViewLocator/ViewHelper) or giving my view knowledge of the Controller view (my main issue with my earlier workaround), any view that wants to deal with login failures or login succeses can do this:

<viewControllers:LoginViewController loginSuccess="doSomething()" loginFailure="playAnnoyingSirenSound()" />

It's a bit like ViewHelper, but backwards enough where it works implicitly instead of explicitly.

Comments

John Allen

John Allen wrote on 02/09/07 10:28 AM

AWSOME!!!! Im laughing that is so great! One of the best Flex tips ive seen!
SOOOOO helpfull!
Keep this stuff coming!
Jay Proulx

Jay Proulx wrote on 02/09/07 11:19 AM

I suppose when the ViewController reacts to a CairngormEvent you could check to see if there is a function defined with the same name as the &quot;registered view&quot; and run it, that's a little hacky, but it would work.

You can actually have any instantiated object listen to CairngormEvents, so you can react to Cairngorm however you want to.
diamondTearz

diamondTearz wrote on 04/14/07 2:40 PM

Thank you so much for this post. I've been on the edge with this problem for days now!
Rider

Rider wrote on 04/19/07 11:48 AM

Could you put an example about ViewController in Cairngorm application?

Thanks.
Bill

Bill wrote on 04/23/07 5:25 PM

Yes, can you please post a code sample? It will help us understand how you have things organized.
Alistair

Alistair wrote on 05/04/07 9:31 AM

I implemented this approach for switching visual elements in and out. Previously I was using the Observe class you mention and altering elements based on the model 'currentState'. At first I wasn't sure but I can see now that this is more flexible as it breaks the handling (in my case anyway) of altering the view into smaller chunks.
Tony Alves

Tony Alves wrote on 05/15/07 11:03 PM

I just read the response by Jay Prouix that you referred. This was more than I could have hoped for. I am as giddy as John up top.

That rocks!

I love this community. What a smart bunch of people you all are!
Tony Alves

Tony Alves wrote on 05/17/07 5:16 AM

I needed to pass on the same type of result event, so I modified the code a little. Let me know if this would help or not.
package com.foo.view.controllers
{
import com.foo.control.ViewController;
import com.foo.control.user.AuthenticationController;
import com.foo.control.user.UserLoginResultEvent;
import com.foo.model.user.User;
import flash.events.Event;

[Event(name=&quot;loginFailed&quot;,type=&quot;com.foo.control.user.UserLoginResultEvent&quot;)]
[Event(name=&quot;loginSuccessful&quot;,type=&quot;com.foo.control.user.UserLoginResultEvent&quot;)]

public class LoginViewController extends ViewController
{
public function LoginViewController() {
addListener(AuthenticationController.USER_LOGIN_RESULT_EVENT, loginResult);
}

private function loginResult(result:UserLoginResultEvent):void {
var u:User = result.user;
var e:UserLoginResultEvent;
if (u.authenticated) {
e=new UserLoginResultEvent(&quot;loginSuccessful&quot;);
} else {
e=new UserLoginResultEvent(&quot;loginFailed&quot;);
}
    e.user=u;

dispatchEvent(e);
}
}
}
Kenny S.

Kenny S. wrote on 06/26/07 11:21 AM

I'm sorry, but am I the only one having trouble with this example? When setting it up, the Flex compiler throws an error that it canot find the property &quot;myEvent&quot; on the controller I had set up.

Don't you have to reference a variable as public somewhere in the class for it to be recognized by MXML?
Kenny S.

Kenny S. wrote on 12/18/07 6:04 AM

After all these months I forgot to post back that I got this elegant and wonderful solution working in every application I have tackled since. Kudos to you Joe!

(Down with spammers!)
VIsweshwar

VIsweshwar wrote on 12/10/08 10:14 PM

Thanks Joe.. A very insightful approach. I am posting the very day I have implemented the same.. Guys if you want you could download a sample implementation at...

www.flex-lcds.blogspot.com

Write your comment



(it will not be displayed)