Why doesn't my signal trigger the command it is mapped to?

rmh's Avatar

rmh

30 Dec, 2013 02:54 PM

I am making a FlexMobile application using Flex 4.11.0 in FlashBuilder 4.7 on Windows 8.1 and cannot make SignalCommandMap work.
My 'libs' contains:
- as3-signals-v0.9-BETA.swc - robotlegs-extensions-SignalCommandMap-v1.0.0b1.swc - robotlegs-framework-v2.1.0.swc

And additional compiler arguements are:
-locale en_US -keep-as3-metadata+=Inject -keep-as3-metadata+=PostConstruct

I have replicated the problem by making a HelloWorld mobile application, see the following code.

<?xml version="1.0" encoding="utf-8"?>
<s:Application xmlns:fx="http://ns.adobe.com/mxml/2009" 
               xmlns:s="library://ns.adobe.com/flex/spark" 
               applicationDPI="160"
               preinitialize="handle_Preinitialize(event)">
    <fx:Script>
        <![CDATA[
            import mx.events.FlexEvent;
            
            import robotlegs.bender.bundles.mvcs.MVCSBundle;
            import robotlegs.bender.extensions.contextView.ContextView;
            import robotlegs.bender.extensions.signalCommandMap.SignalCommandMapExtension;
            import robotlegs.bender.framework.api.IContext;
            import robotlegs.bender.framework.impl.Context;
            
            private var context:IContext;
            
            protected function handle_Preinitialize(event:FlexEvent):void
            {
                context = new Context()
                    .install(MVCSBundle, SignalCommandMapExtension)
                    .configure(AppConfig)
                    .configure(new ContextView(this));
            }
            
            protected function handleClick(event:MouseEvent):void
            {
                var signal:HelloWorldSignal = new HelloWorldSignal();
                
                signal.dispatch();
            }
            
        ]]>
    </fx:Script>
    <s:Button label="Click me" click="handleClick(event)" />
</s:Application>

// AppConfig.as

package
{
    import robotlegs.bender.extensions.signalCommandMap.api.ISignalCommandMap;
    import robotlegs.bender.framework.api.IConfig;
    
    public class AppConfig implements IConfig
    {
        [Inject]
        public var signalCommandMap:ISignalCommandMap;

        public function configure():void
        {
            signalCommandMap
                .map(HelloWorldSignal)
                    .toCommand(HelloWorldCommand);
        }
    }
}

// --- HelloWorldSignal.as ---

package
{
    import org.osflash.signals.Signal;
    
    public class HelloWorldSignal extends Signal
    {
        public function HelloWorldSignal(...parameters)
        {
            super(parameters);
        }
    }
}

// --- HelloWorldCommand.as ---

package
{
    import robotlegs.bender.bundles.mvcs.Command;
    
    public class HelloWorldCommand extends Command
    {
        override public function execute():void
        {
            trace("Hello World");
        }
    }
}

The console output after clicking the button is:

[SWF] HelloWorld.swf - 3,515,674 bytes after decompression
646 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object VigilanceExtension]
647 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object InjectableLoggerExtension]
647 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object ContextViewExtension]
648 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object EventDispatcherExtension]
648 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object ModularityExtension]
648 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object DirectCommandMapExtension]
649 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object EventCommandMapExtension]
649 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object LocalEventMapExtension]
649 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object ViewManagerExtension]
650 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object StageObserverExtension]
650 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object MediatorMapExtension]
651 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object ViewProcessorMapExtension]
652 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object StageCrawlerExtension]
652 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension [object StageSyncExtension]
653 DEBUG Context-0-7f [object ConfigManager] Not yet initialized. Queuing config class [class ContextViewListenerConfig]
653 DEBUG Context-0-7f [object ExtensionInstaller] Installing extension SignalCommandMapExtension-1-6b
653 DEBUG Context-0-7f [object ConfigManager] Not yet initialized. Queuing config class [class AppConfig]
653 DEBUG Context-0-7f [object ConfigManager] Not yet initialized. Queuing config object [object ContextView]
654 DEBUG Context-0-7f [object ContextViewExtension] Mapping HelloWorld0 as contextView
655 DEBUG Context-0-7f [object ModularityExtension] Context has a ViewManager. Configuring view manager based context existence watcher...
661 DEBUG Context-0-7f [object ModularityExtension] Context view is not yet on stage. Waiting...
661 DEBUG Context-0-7f [object StageSyncExtension] Context view is not yet on stage. Waiting...
842 DEBUG Context-0-7f [object ModularityExtension] Context view is now on stage. Continuing...
842 DEBUG Context-0-7f [object ModularityExtension] Context configured to inherit. Broadcasting existence event...
843 DEBUG Context-0-7f [object StageSyncExtension] Context view is now on stage. Initializing context...
844 INFO Context-0-7f Context-0-7f Initializing...
849 DEBUG Context-0-7f [object StageObserverExtension] Creating genuine StageObserver Singleton
849 DEBUG Context-0-7f [object ConfigManager] Now initializing. Instantiating config class [class ContextViewListenerConfig]
852 DEBUG Context-0-7f [object ViewManagerBasedExistenceWatcher] Adding context existence event listener to container HelloWorld0
852 DEBUG Context-0-7f [object ConfigManager] Now initializing. Instantiating config class [class AppConfig]
856 DEBUG Context-0-7f [object ConfigManager] Now initializing. Injecting into config object [object ContextView]
856 INFO Context-0-7f Context-0-7f Initialize complete
856 DEBUG Context-0-7f [object StageCrawlerExtension] ViewManager is installed. Checking for managed containers...
856 DEBUG Context-0-7f [object StageCrawlerExtension] StageCrawler scanning container HelloWorld0 ...
858 DEBUG Context-0-7f [object StageCrawlerExtension] StageCrawler finished scanning HelloWorld0

Thank you for your help.

  1. Support Staff 1 Posted by Ondina D.F. on 31 Dec, 2013 11:14 AM

    Ondina D.F.'s Avatar

    Why doesn't my signal trigger the command it is mapped to?

    Because the signal you've created in your view like this:

    var signal:HelloWorldSignal = new HelloWorldSignal();
    

    is a new instance of HelloWorldSignal, different from the one known by the Injector through the mapping:

    signalCommandMap
                    .map(HelloWorldSignal)
                        .toCommand(HelloWorldCommand)
    

    Let the Injector create or get an instance of HelloWorldSignal, either by injecting the Signal into the class, that needs it:

    [Inject]
    public var helloWorldSignal: HelloWorldSignal;
    

    or like this:

    var helloWorldSignal: HelloWorldSignal = injector.getInstance(HelloWorldSignal);
    

    It is possible to inject a signal into a view. You need a mapping for your signal and the viewProcessorMap to let the injector know which view needs injection ( see the link below for details about viewProcessorMap):

    //injector.map(HelloWorldSignal).asSingleton();
    signalCommandMap
        .map(HelloWorldSignal)
        .toCommand(HelloWorldCommand)
    viewProcessorMap.map(HelloWorldView).toInjection();
    

    Even though it is possible to trigger a command by dispatching a signal from a view, I wouldn't recommend this approach. It is better to let the mediator of a view communicate with other parts of your application on behalf of its view.

    Similar discussions:
    http://knowledge.robotlegs.org/discussions/robotlegs-2/8018-why-i-c...
    http://knowledge.robotlegs.org/discussions/robotlegs-2/5766-automag...

    Robotlegs-Internals:
    https://github.com/robotlegs/robotlegs-framework/wiki/Robotlegs-Int...

    ViewProcessorMap:
    https://github.com/robotlegs/robotlegs-framework/tree/master/src/ro...

    Hope that helps
    Ondina

  2. 2 Posted by rmh on 31 Dec, 2013 12:47 PM

    rmh's Avatar

    Thank you very much, it works. As simple as it is, it slipped my mind.

    I have a follow up question on best practice on using Signals. In the following mediator I have mixed events and signals. I added an event listener in the mediator to keep it out of the view and I didn't use a signal in the view as this also clutters the view in my mind.

    What is considered best practice?

    package
    {
        import flash.events.MouseEvent;
        
        import robotlegs.bender.bundles.mvcs.Mediator;
    
        public class HelloWorldMediator extends Mediator
        {
            [Inject]
            public var view:HelloWorld;
            
            [Inject]
            public var helloWorldSignal:HelloWorldSignal;
            
            override public function initialize():void
            {
                view.button.addEventListener(MouseEvent.CLICK,handle_Click);
            }
            
            private function handle_Click(e:MouseEvent):void
            {
                helloWorldSignal.dispatch();
            }       
        }
    }
    
  3. Support Staff 3 Posted by Ondina D.F. on 31 Dec, 2013 02:11 PM

    Ondina D.F.'s Avatar

    Glad to hear that it worked!

    A good practice would be to dispatch a custom event from the view, in the handler of MouseEvent.CLICK when the button is clicked:

    dispatchEvent(new SomeEvent (SomeEvent.SOME_TYPE));

    The mediator adds a listener like so:

    addViewListener(SomeEvent.SOME_TYPE, onSomethingHappened, SomeEvent);

    The custom event should override clone() if you want it to be re-dispatched by the mediator:

    addViewListener(SomeEvent.SOME_TYPE, dispatch, SomeEvent);

    The advantage of using addViewListener is that when the mediator gets destroyed(when the view is removed from stage) the event listeners will be removed automatically. That is important in case you want your views/mediators to get garbage collected.

    In your example:

    view.button.addEventListener(MouseEvent.CLICK,handle_Click);

    if you don't remove the listener manually in the overridden destroy() method of the mediator, the view won't be eligible for gc, because there will be still a reference to it, keeping it alive. Same with signals, you'd have to remove the signals manually inside destroy().

    Another advantage of using custom events dispatched from views is that the mediator doesn't have to care whether the button dispatches a Mouse or a Touch event. This way your mediators become portable, you can use the same mediator for a browser, desktop or mobile app. The views are also encapsulated, portable and don't depend on any framework or library to be functional.

    If you want to use signals, you can take a look at the demo linked below to see how you can dispatch signals from views, how the mediator listens to them and so on:

    https://github.com/robotlegs/robotlegs-demo-TodoList

  4. 4 Posted by rmh on 31 Dec, 2013 10:23 PM

    rmh's Avatar

    Thank you very much for a swift and comprehensive answer.

  5. rmh closed this discussion on 31 Dec, 2013 10:24 PM.

Comments are currently closed for this discussion. You can start a new one.

Keyboard shortcuts

Generic

? Show this help
ESC Blurs the current field

Comment Form

r Focus the comment reply box
^ + ↩ Submit the comment

You can use Command ⌘ instead of Control ^ on Mac