Archive for category swiz

Swiz EventHandler Filtering With Pub/Sub Topics - Part 1

We tend to build many Flex applications with modules, so having the ability to broadcast and listen for messages based on the scope attribute for both Dispatchers and EventHandlers is essential in Swiz.

UPDATE: Part 2 has Swiz framework code changes and a more robust solution.

If you don’t know about Swiz Event Scoping, then read on; otherwise, skip to the section labeled Filtering With Topics below.

Background

You can read the actual documentation from Swiz in the last link, but the gist is that you can tell Swiz to either broadcast events on a “global” level (using the highest level Application object) or on a “local” level (using the Module itself) and then listen to those events in the same fashion. This allows you to tell modules to listen to events only from themselves and not other modules (that might have the same events) or to listen to all events from your main, shell application and/or all other modules. Let’s look at a quick example by assuming the dispatching code is in LoginViewMediator and the handling code is in LoginController in a Module.

Local Event Dispatching

Local Event Dispatching From LoginViewMediator

/**
 * Allows this class to dispatch local events. Only methods using an
 * EventHandler metatadata tag with the property of scope="local" can
 * handle this event.
 *
 * <p>
 * The event dispatcher is injected by Swiz due to the [Dispatcher] metadata
 * and the class member's type of IEventDispatcher.
 * </p>
 */
[Dispatcher(scope="local")]
public var localDispatcher:IEventDispatcher;
...
public function login(userName:String, password:String):void
{
	logger.debug("login");

	var appEvt:AppEvent;

	appEvt = new AppEvent(AppEvent.SERVICE_LOGIN);
	appEvt.username = userName;
	appEvt.password = password;
	this.localDispatcher.dispatchEvent(appEvt);
}

Local Event Handling in LoginController

[EventHandler(event="AppEvent.SERVICE_LOGIN", properties="username, password", scope="local")]
public function login(username:String, password:String):void
{
	logger.debug("login: username = " + username + ", password = " + password);
}

If we had other Modules with this same EventHandler statement in their LoginController they would not fire because we specified the scope as local. If we change the scope property to global and the AppEvent is in a common library that all Modules can use, then they would each hear this event and all fire, which in many cases is not what you want. Just so we have a full understanding of what’s going on, let’s show an example of broadcasting global events in a similar fashion:

Global Event Dispatching

Global Event Dispatching From ModuleA

/**
 * Allows this class to dispatch global events. Only methods using an
 * EventHandler metatadata tag with the property of scope="global" can
 * handle this event. This means other modules and even the shell application
 * can handle this event if we want.
 *
 * <p>
 * The event dispatcher is injected by Swiz due to the [Dispatcher] metadata
 * and the class member's type of IEventDispatcher.
 * </p>
 */
[Dispatcher(scope="global")]
public var globalDispatcher:IEventDispatcher;
...
public function sayHello():void
{
	logger.debug("sayHello");

	var appEvt:AppEvent;

	appEvt = new AppEvent(AppEvent.SAY_HELLO);
	appEvt.hello = "Hello World!";
	this.globalDispatcher.dispatchEvent(appEvt);
}

Global Event Handling in Module B

[EventHandler(event="AppEvent.SAY_HELLO", properties="hello", scope="global")]
public function hello(hello:String):void
{
	logger.debug("hello = " + hello);
}

Filtering With Topics

Again, this is awesome…but what if you want to broadcast a message that you only want some of the modules to hear? You could obviously put in some simple logic in the event handler that determines if your Module was supposed to listen to the event based on a parameter passed in the EventHandler MetaData, but this could potentially become a large and unwieldy task that you’ll need to apply to all new modules…so what if we introduce filtering of Swiz events via a topic attribute in the EventHandler metadata and a corresponding topic property in our dispatched event?

To do this, we’ll need to do some quick monkey patching to Swiz (by creating the same package structure and adding the same classes we want to change that exist in the Swiz SWC). First, let’s add the topic property to the org.swizframework.metadata.EventHandlerMetadataTag. Add the topic property:

protected var _topic:String;
public function get topic():String
{
	return _topic;
}

Then add the following to the method override public function copyFrom( metadataTag:IMetadataTag ):void

if( hasArg( "topic" ) )
    _topic = getArg( "topic" ).value;

Next we’ll edit the method public function handleEvent( event:Event ):void in org.swizframework.utils.event.EventHandler. Add the following right after the first if() statement:

// look for a topic -- if the one exists in the EventHandler MeataData and
// the event does not have one or the value does not equal the EventHandler's
// then we'll filter out this event and not allow it
if(metadataTag.topic != null)
{
	if(event["topic"] == null)
	{
		return;
	}
	else if(event["topic"] != metadataTag.topic)
	{
		return;
	}
}

That’s it. Let’s give it a whirl by using a global dispatcher again, but adding a topic property to the event and a topic attribute to the EventHandler metadata.

Global Event Dispatching From ModuleA

/**
 * Allows this class to dispatch global events. Only methods using an
 * EventHandler metatadata tag with the property of scope="global" can
 * handle this event. This means other modules and even the shell application
 * can handle this event if we want.
 *
 * <p>
 * The event dispatcher is injected by Swiz due to the [Dispatcher] metadata
 * and the class member's type of IEventDispatcher.
 * </p>
 */
[Dispatcher(scope="global")]
public var globalDispatcher:IEventDispatcher;
...
public function sayHello():void
{
	logger.debug("sayHello");

	var appEvt:AppEvent;

	appEvt = new AppEvent(AppEvent.SAY_HELLO);
	appEvt.hello = "Hello World!";
        appEvt.topic = "wasiTopic";
	this.globalDispatcher.dispatchEvent(appEvt);
}

Global Event Handling in Module B

[EventHandler(event="AppEvent.SAY_HELLO", properties="hello", scope="global", topic="wasiTopic")]
public function helloWithTopic(hello:String):void
{
	logger.debug("hello = " + hello); // WORKS
}

[EventHandler(event="AppEvent.SAY_HELLO", properties="hello", scope="global")]
public function helloWithOutTopic(hello:String):void
{
	logger.debug("hello = " + hello); // NOPE
}

[EventHandler(event="AppEvent.SAY_HELLO", properties="hello", scope="global", topic="fooTopic")]
public function helloWithOutTopic(hello:String):void
{
	logger.debug("hello = " + hello); // NOPE
}

UPDATE: Part 2 has Swiz framework code changes and a more robust solution.

Post to Twitter Tweet This Post

, , , , ,

1 Comment

New AbstractViewMediator For Popups Using Swiz 1.0 RC1

This post builds on my last post regarding the use of the ViewMediator pattern with Swiz. One of the things I hadn’t tested when I wrote the original AbstractViewMediator (AVM) was popups. Once I started using this base class in a couple projects I realized I had a pretty big hole that I quickly need to fix.

Download the new and improved AbstractViewMediator.

Normally, views are added to the display list, but popups are actually added to the SystemManager so they don’t fire off the flash.events.Event.ADDED_TO_STAGE event that’s mediated in the setView() method in my AbstractViewMediator and that’s the core piece of logic behind the entire AbtractViewMediator. See the last post for details.

So how would I connect my views to my VMs? The key was to make the AbstractViewMediator actually create the popup via the following method:

public function createPopupView(
								view:IFlexDisplayObject,
								parent:DisplayObject,
								modal:Boolean = false,
								childList:String = null,
								moduleFactory:IFlexModuleFactory = null
								):IFlexDisplayObject
{
	this.setView(view);

	PopUpManager.addPopUp(view, parent, true); // window:IFlexDisplayObject,parent:DisplayObject,modal:Boolean = false,childList:String = null,moduleFactory:IFlexModuleFactory = null
	PopUpManager.centerPopUp(view);

	return view;
}

If you look at the method signature you’ll see that I pass in a reference to the view we’re creating and then call the all important public function setView(value:*):void method that’s usually called by the mediated event flash.events.Event.ADDED_TO_STAGE. This is what creates the connection between a VM and a View. This also brings up a new question — who calls this method?

In order for the VM to be hooked up to the correct view, we’ll need to make sure we call the createPopupView(...) method on our concrete VM implementation…hmm…so how do we get a hold of that…ahh yes, the nice guys at Swiz gave us access to it via the list of beans that this instance of Swiz has. Let’s learn by example.

We’ll imagine we need to open a popup from MainView after clicking the “Get Users Button”. Naturally, our MainViewMediator mediates the button’s click event and calls this functional method:

/**
 * Open the create user view.
 */
public function openCreateUserView():void
{
	var view:CreateUserView;
	var vm:CreateUserViewMediator;

	view = new CreateUserView();
	vm = this.getViewMediatorBeanById("createUserViewMediator") as CreateUserViewMediator;

	vm.createPopupView(view, this.view, true);
}

The key line is where we get a handle to the CreateUserViewMediator via the method getViewMediatorBeanById(...) in the AbstractViewMediator:

/**
 * Helper method that returns a view mediator managed by Swiz by it's ID.
 *
 * @param	id	The unique ID for the view mediator as defined in the BeanLoader.
 */
public function getViewMediatorBeanById(id:String):AbstractViewMediator
{
	var vm:AbstractViewMediator = this.swiz.beanFactory.getBeanByName(id).source as AbstractViewMediator;

	return vm;
}

/**
 * Helper method that returns a view mediator managed by Swiz by it's ID.
 *
 * @param	clazz	The class type for our view mediator.
 */
public function getViewMediatorBeanByType(clazz:Class):AbstractViewMediator
{
	var vm:AbstractViewMediator = this.swiz.beanFactory.getBeanByType(clazz).source as AbstractViewMediator;

	return vm;
}

This method in the AVM also assumes it has a handle to the instance of Swiz that contains the beans. This is done by implementing the ISwizAware interface in the AVM.

/**
 * Creates a reference to the instance of Swiz this bean is in. This
 * allows us to get a hold of manager beans by using:
 * <code>var vm:MyViewMediator = swiz.beanFactory.getBeanByName("myViewMediator").source as MyViewMediator;</code>
 */
public function set swiz(swiz:ISwiz):void
{
	this._swiz = swiz;
}
public function get swiz():ISwiz
{
	return this._swiz;
}
protected var _swiz:ISwiz;

There you have it. All the pieces. If you create popups this way you’ll get the expected VM to View connection.

Download the new and improved AbstractViewMediator.

Post to Twitter Tweet This Post

, , , , , ,

5 Comments