Modular Application

mbarjawi's Avatar

mbarjawi

22 Sep, 2012 05:11 AM

Hello,

I am trying to create a Robotlegs 2 modular application... but having some difficulties..
I can't figure out the right sequence of things... and I can't find an examples online.
was wondering if you can help me with a small example.

Thanks,

  1. Support Staff 1 Posted by Ondina D.F. on 23 Sep, 2012 09:52 AM

    Ondina D.F.'s Avatar

    Hello,

    A few months ago I put a project (AIR) on github. I was just experimenting with modules and popups in rl2.

    It’s not a demo!!!!!

    I wanted to provide documentation for the project, but I haven’t got a chance to finish it yet.
    You can take a look at LoginModule
    , which may be easier to understand than the ChatModule.

    Use it at your own risk ;)

    I am trying to create a Robotlegs 2 modular application... but having some difficulties.. I can't figure out the right sequence of things.

    Can you be more specific?

    Cheers,
    Ondina

  2. 2 Posted by mbarjawi on 24 Sep, 2012 08:02 PM

    mbarjawi's Avatar

    Thank you Ondina for your help.

    I am trying to create a simple module, load it in my Main Application, and then be able to exchange data and events between both sides. In future, I will have more than one module in my main application, and I would want them to interact with each other as well.

    In your project I can see that you are utilizing the ScopedEventDispatcherExtension. It seems that in each module I need to extend this extension and use the scope names that this module can listen and dispatch to. There is no readme.md file for this extension so I am not really 100% sure how we use it.

    I am not sure how to route the events to a certain scope. For example, if I am in the global scope and want to send an event to the channelOne scope... or the other way around.

    Looking further into the framework, I can see the ModularityExtension which requires the ContextViewExtension... but again, other than adding them to the context.. I have no idea if they are the ones that I need, or even how to use them.

    Thanks for your help,

  3. 3 Posted by Vj3k0 on 25 Sep, 2012 07:54 AM

    Vj3k0's Avatar

    From what I've gathered by looking at the code, if you use ModularityExtension, when you add your module to your application it will actually put reference to parent injector in your module...

    childContext.injector.parentInjector = injector;

    ...where child context is your module.

    I didn't try it out ye, just sharing my observations. I'm at the point where I'll need to start working with modules too, so I'm also interested in the subject.

    Regards,
    Vjeko

  4. Support Staff 4 Posted by Ondina D.F. on 25 Sep, 2012 10:35 AM

    Ondina D.F.'s Avatar

    Hi guys,

    ModularityExtension.as
    “This extension allows a context to inherit dependencies from a parent context, and/or expose its dependencies to child contexts.”
    and the
    readme on github

    For a more detailed explanation about ModularityExtension and ScopedEventDispatcherExtension please look at Shaun’s answers:

    https://groups.google.com/forum/?fromgroups=#!topic/robotlegs/jj9Y7...

    It seems that in each module I need to extend this extension and use the scope names that this module can listen and dispatch to.

    I was experimenting with different settings in the different contexts, and I forgot to comment out what’s not needed.

    I used the MVCSBundle, but the following extensions would have been enough to configure LoginModule:

    childContext.extend(ContextViewExtension);
    childContext.extend(StageSyncExtension);
    
    childContext.extend(
    EventCommandMapExtension, 
    EventDispatcherExtension, 
    CommandMapExtension, 
    MediatorMapExtension, 
    LocalEventMapExtension);
    
    childContext.extend(ModularityExtension);
    

    If the ScopedEventDispatcherExtension is installed like this in the parent context

    context.extend(ScopedEventDispatcherExtension);
    

    then, LoginModule’s context will inherit parent’s dependencies and have access to the shared dispatcher. All you have to do is to use named injections for the shared dispatcher with the default ‘global’ to allow the communication between shell and module.

    [Inject (name="global")]
        
    public var defaultDispatcher:IEventDispatcher;
    

    ScopedEventDispatcherExtension.as

    public function ScopedEventDispatcherExtension(... names)
    {
        _names=(names.length > 0) ? names : ["global"];
    }
    

    If you wanted a custom name for the named injection, then in the parent context you’d do this:

    context.extend(new ScopedEventDispatcherExtension("login"));
    

    and in LoginModule and in the shell’s classes that need to share the same dispatcher:

    [Inject (name="login")]    
    public var loginDispatcher:IEventDispatcher;
    

    I am not sure how to route the events to a certain scope. For example, if I am in the global scope and want to send an event to the channelOne scope... or the other way around.

    ChatModule is just an experiment. It’s a bit hard to understand without explanations.

    In fact, it’s quite easy to define the scopes.

    Shell< == >Module communication
    If you look at LoginModule:

    LoginLocallyService (Module)

    [Inject(name="global")]    
    public var defaultDispatcher:IEventDispatcher;
    
    defaultDispatcher.dispatchEvent(new AlertEvent(AlertEvent.ERROR_ALERT, faultMessage));
    

    Here, you dispatch the event on the shared dispatcher.

    AlertMediator (Shell)

    [Inject(name="global")]        
    public var defaultDispatcher:IEventDispatcher;
    
    eventMap.mapListener(defaultDispatcher, AlertEvent.ERROR_ALERT, onErrorAlert, AlertEvent);
    

    Here, you map the listener as defaultDispatcher (the „global“, shared dispatcher between shell and module).

    Module < == > Module communication
    If you had a parent’s context configuration like this:

    context.extend(new ScopedEventDispatcherExtension("channelOne"));
    

    and 2 Modules inheriting from the same parent, then all you need is to inject the shared dispatcher into both modules (where you need it), and they will communicate through that shared dispatcher.

    [Inject(name="channelOne")]
    public var someSharedDispatcher:IEventDispatcher;
    

    map listeners like this:

    eventMap.mapListener(someSharedDispatcher, InterModularEvent.MODULE_TO_MODULE, onIntermodularMessage, InterModularEvent);
    

    and dispatch the events like this:

    private function onModuleToModule(event:InterModularEvent):void
    {           
        someSharedDispatcher.dispatchEvent(event);
    }
    

    Hopefully my explanations made sense :)

    Ondina

  5. 5 Posted by mbarjawi on 25 Sep, 2012 07:19 PM

    mbarjawi's Avatar

    Thanks again Ondina for your detailed explanation.

    After deeply examining your project I was able to come to the same conclusions that you explained in your post. Your post helped me further clarify how things work.

    I have created a small project (link here) that has two modules and a shell, it routes messages to: globally to everyone, to modules only, to module 1 only, and to module 2 only. Could you please take a quick look at the project and let me know if I am doing it the right way (with minimal code possible)... or is there code that can (and maybe should) be deleted?

    I made the following test, in the module 1 config file, I initially have the context defined this way:

    context = new Context()
        .extend( MVCSBundle )
        .extend( ContextViewExtension )
        .extend( ModularityExtension )
        .extend( new ScopedEventDispatcherExtension( "global", "moduleOnly", "toModule1" ) )
        .configure( _contextView );
    

    However, when I remove the middle three lines (extend ContextViewExtension, ModularityExtension, and ScopedEventDispatcherExtension) everything still works fine... so I thought that was strange. Then I thought maybe because the MVCSBundle has the ContextViewExtension and the ModularityExtension installed in it... however it doesn't have ScopedEventDispatcherExtension. Do you have any idea why it still works without having the ScopedEventDispatcherExtension defined in the child contexts?

    My guess is that since everything that is injected in the main (a.k.a. shell) context is available in the children contexts (probably because of the ModularityExtension but not sure why)... so does the named IEventDispatchers that were injected in the main context.

    The other thing that I am trying to understand here is the ModularityExtension. I understand that (this extension allows a context to inherit dependencies from a parent context, and/or expose its dependencies to child contexts)... however what does this mean? what are those dependencies?? is this why in my child context I can see injections that were defined in the main context? if so, then why I can't do it the other way? If I define an injection in a module, I cannot get access to this injection in the main context or in the other sibling module.

    Thanks a lot for your efforts,

  6. 6 Posted by Vj3k0 on 26 Sep, 2012 06:20 AM

    Vj3k0's Avatar

    If you are using MVCSBundle you don't have to explicitly add ContextViewExtension and ModularityExtension since they are included in it.

    Are you positive that it works without ScopedEventDispatcherExtension? Because I see that you are using named injection in your mediators, so I'm wondering how it would work if no injection is mapped (I think SWFSuspender should throw you an error about missing injection).

    As for ModulartiyExtension... If you take a peek at the class itself you can see that there are 2 Booleans: inherit and expose. What inherit will do is, it will basically register its existence as module (to any parent that may be listening), and expose will setup listeners to accept any module.
    So when some module is added to its context it will run:
    childContext.injector.parentInjector = injector;

    And from what I gathered, this will say that all injections defined in context of a parent will be also available in module. But not the other way around.

    So in your case you could share event dispatcher from parent context in both module and parent.

    Regards,
    Vjeko

  7. Support Staff 7 Posted by Ondina D.F. on 26 Sep, 2012 08:36 AM

    Ondina D.F.'s Avatar

    @Vjeko Your posts keep landing in the spam folder for some reason. Good thing I’ve developed the habit of looking into this folder periodically, to see if there are ‚false positives’ ;)

    Are you positive that it works without ScopedEventDispatcherExtension? Because I see that you are using named injection in your mediators, so I'm wondering how it would work if no injection is mapped (I think SWFSuspender should throw you an error about missing injection).

    It works.
    I think that’s because this:

    In parent’s context
    context.extend(new ScopedEventDispatcherExtension("hello“));

    is the same as this:

    In parent’s context:
    injector.map(IEventDispatcher, "hello").toValue(new EventDispatcher());

    In a Module’s class:
    [Inject(name="hello")]
    public var helloDispatcher:IEventDispatcher;

    @mbarjawi I am glad to be helping you!
    I’ll take a look at your code and I’ll report back later today with answers or comments.

    Ondina

  8. 8 Posted by Vj3k0 on 26 Sep, 2012 08:49 AM

    Vj3k0's Avatar

    @Ondina

    Aha I overlooked module configs, but only saw RL2ModularCommunicationsConfig. That's why I didn't see how it works.

    @mbarjawi

    Anyhow, yea, in RL2ModularCommunicationsConfig you are defining mapping:
    .extend( new ScopedEventDispatcherExtension( "global", "moduleOnly", "toModule1", "toModule2" ) ) This will now be mapped to your context injector. And with ModularityExtension you make this mapping available to your modules as well. This is why it works.

    Regards,
    Vjeko

  9. Support Staff 9 Posted by Ondina D.F. on 26 Sep, 2012 12:00 PM

    Ondina D.F.'s Avatar

    Just to recap what was already said in the course of our discussion:

    =The main app(RL2ModularCommunications) loads 2 Modules: ModuleView1
    ModuleView2

    RL2ModularCommunicationsConfig:
    context.extend(new ScopedEventDispatcherExtension( "global", "moduleOnly", "toModule1", "toModule2" ) )

    The 2 modules inherit the dispatchers installed in main RL2ModularCommunicationsConfig, so there is no need to extend
    ScopedEventDispatcherExtension in ModuleView1Config
    and ModuleView2Config.

    =Let’s say ModuleView1 would load another module, SubModuleOneView.

    SubModuleOneView would inherit ModuleView1’s dependencies and therefore it could communicate with the shell and other modules.

    If you wanted ModuleView1 and SubModuleOneView to have their „private“ channel, you’d do this in ModuleView1Config:

    context.extend( new ScopedEventDispatcherExtension("subModuleChannel"))

    and inject the shared dispatcher into both mediators, ModuleView1Mediator and SubModuleOneMediator:

    [Inject(name="subModuleChannel")] public var subModuleChannelDispatcher:IEventDispatcher;

    You don’t need to extend( new ScopedEventDispatcherExtension( "subModuleChannel")) in SubModuleConfig, because it will inherit the dispatcher from ModuleView1 in addition to all the dispatchers defined in the main context.

    =If you had the following in RL2ModularCommunicationsConfig

    .extend( new ScopedEventDispatcherExtension("global", "moduleOnly", "toModule1", "toModule2", "subModuleChannel") )

    then all of the child contexts down the tree would inherit the dispatchers, and you could inject them just into the classes which need them. Say, if you don’t inject

    [Inject(name="global")] public var globalDispatcher:IEventDispatcher;

    into SubModuleOneMediator, then it won’t receive any messages dispatched on that dispatcher, and, of course, it won’t be able to dispatch any events on it.

    =MVCSBundle As Vjeko said, and as I mentioned in a previous post, you either use the MVCSBundle, or your install only the extensions that you need in your project, or MVCSBundle plus other extensions that are not included in the bundle. You can, in fact, create your custom MVCSBundle, extending only what you need for a certain project.

    So, if you have this in RL2ModularCommunicationsConfig

    context = new Context()
    .extend( MVCSBundle ) .extend( new ScopedEventDispatcherExtension( "global", "moduleOnly", "toModule1", "toModule2") ) .configure( _contextView );

    then ModuleView1Config and ModuleView2Config would look like this:

    context = new Context()
    .extend( MVCSBundle) .configure( _contextView );

    @mbarjawi your example looks fine! I know that your example was intended just to provide a common base for our discussion, but if you have time to refine it, it could become a demo :)
    Just a suggestion: I wouldn’t listen for MouseEvent.CLICK in the mediators. Instead I would dispatch a custom event from View (can be the same TextMessageEvent) with the message and selected dispatcher in the payload. Also, instead of the switch statement, I’d use a dictionary to map the dispatchers’ names to the shared dispatchers.

    Hehe, I know, I should improve my example too, but I’m afraid it won’t happen any time soon.

    I guess Vjeko has already answered your question regarding the ModulartiyExtension, right?

    If you want to know more about parent and child injectors, you can check out the links I provided in this thread:
    http://knowledge.robotlegs.org/discussions/questions/983-sharing-mo...

    Sorry, if you knew all this already :)

    Ondina

  10. 10 Posted by mbarjawi on 26 Sep, 2012 09:02 PM

    mbarjawi's Avatar

    Thank you @Vjeko and @Ondina for your help and explanation. I better understand how things work now.

    I have also refined my example: RL2ModularCommunications, so please take a look and let me know if you there is something else that I should modify before adding it as a demo. It might be helpful for someone out there looking for the same help as I did.

    About ModularityExtension, I think I understand it better now. If my understanding is correct, then the shell should actually extend

    .extend( new ModularityExtension( false, true ) );
    

    because the Shell doesn't inherit anything from anybody, and it needs to expose itself to the modules.

    In addition, the modules should extend:

     .extend( new ModularityExtension( true, false ) );
    

    This is true for my example, right?... if someone else has a different scenario of course these extensions should be defined differently. However, I just wrote those two statements to check if my understanding of ModularityExtension is correct.

    I think the comments in the ModularityExtension class itself are not clear in this regard. If you read the comments on the constructor, it says:

     /**
      * Modularity
      *
      * @param inherit Should this context inherit dependencies?
      * @param expose Should this context expose its dependencies?
      */
    

    Which I think was confusing because I thought for a while that if I set this to (true, true) then it will become a two way communication.. meaning that my module would be able to see the Shell dependencies AND my Shell would be able to see my module's dependencies (since they are exposed).

    However, now that I read it again, and take a look at the comment on the class itself, it clearly says:

     and/or expose its dependencies to child contexts
    

    Just a final check of my understanding, there is nothing currently in the framework that allows the child context to share its dependencies with parent context.. right? and from the links posted above, @Stray talks about this as not being logical as the Module should be a separate entity or something like that, right?

  11. Support Staff 11 Posted by Ondina D.F. on 27 Sep, 2012 01:34 PM

    Ondina D.F.'s Avatar

    Hi,

    RL2ModularCommunications

    I took a look at your modified code, but I haven’t got the time to try it out yet.
    Anyway, communicationChannels dictionary looks good!!

    One thing, though:

    You’re aggregating an instance of the EventDispatcher class in your mediators:

    EventDispatcher( dispatchChannel[ "global" ] ).addEventListener( TextMessageEvent.TEXT_MESSAGE, messageReceived );
    

    In case you wanted to unload the module and you wanted it to get gc-ed (garbage collected), you’d have to override mediator’s destroy() and unmap those listeners manually.
    Listeners mapped through the eventMap get unmapped automatically when the mediator gets removed (see rl’s Mediator.as destroy()), which is very convenient, in my opinion.

    So, if you’ll have to deal with gc (maybe in another project) you could use mappings like these:

    eventMap.mapListener(dispatchChannel[ "global" ] , TextMessageEvent.TEXT_MESSAGE, messageReceived );
    
    eventMap.mapListener(dispatchChannel[ "moduleOnly" ] , TextMessageEvent.TEXT_MESSAGE, messageReceived );
    
    eventMap.mapListener(dispatchChannel[ "toModule1" ] , TextMessageEvent.TEXT_MESSAGE, messageReceived );
    

    Or

    for(var dispatcherName:String in dispatchChannel)
    {
       eventMap.mapListener(dispatchChannel[ dispatcherName ] , TextMessageEvent.TEXT_MESSAGE, messageReceived );
    }
    

    and dispatch like this:

    dispatchChannel[ event.channel ].dispatchEvent( event );
    

    And the listeners will be removed automatically when the module gets unloaded.

    It’s important to dereference any objects that could keep the Mediator and/or the View alive. Removing event listeners is part of that cleaning up process.

    ModularityExtension

    Yes, you’re right about the ModularityExtension’s booleans.

    If Main display object loads Module1, which loads Submodule1, then the settings would look like this:

    =MainConfig

    context.extend( new ScopedEventDispatcherExtension( "global", "moduleOnly", "toModule1", "toModule2" ) )
    context.extend(new ModularityExtension(false, true));

    =ModuleView1Config

    context.extend( new ScopedEventDispatcherExtension( "subModuleChannel");
    context.extend(new ModularityExtension(true, true));

    =SubModule1Config

    context.extend(new ModularityExtension(true, false));

    Module1 will inherit Main’s dispatchers.
    SubModule1Config will inherit only the dispatcher installed in Module1.

    Main and Module1 have to set expose to true, but since SubModule1Config has no children, it doesn’t need to expose its dependencies.
    If you’d set expose to false in Main and Module1, the injector would complain about missing a mapping or something. Same would happen if Module1‘s and SubModule1’s inherit param would be set to false.

    I haven’t tried it out yet, so hopefully I’m not wrong about it.

    Which I think was confusing because I thought for a while that if I set this to (true, true) then it will become a two way communication.. meaning that my module would be able to see the Shell dependencies AND my Shell would be able to see my module's dependencies (since they are exposed).

    Right, the dependencies are exposed only to the children.
    As in real life, parents shouldn’t know what their (adult) children are doing in their own houses ;)
    If the children want to let their parents know about something happening in their lives, they can give them a call (dispatch an event). The “inherited” telephone(shared dispatcher) should be the two-way communication channel between parents and children. It’s not the best analogy, but you’ll get the point.

    And yes, a module should be a separate entity, encapsulated, reusable in a variety of combinations, separately testable… But, you know all this, so what am I talking about? :)
    Maybe your question is “If children are allowed to hide their internal working, why are parents forced to expose theirs?”

    Oh, sorry, I’m running out of time. I’ll continue later today or tomorrow, if you need more clarification (from me) on any of this or if you have any other questions. But, actually, the discussion is open to anyone who wants to share their rl2 opinions/experience.

    Cheers,
    Ondina

  12. Support Staff 12 Posted by Ondina D.F. on 28 Sep, 2012 07:28 AM

    Ondina D.F.'s Avatar
  13. 13 Posted by mbarjawi on 28 Sep, 2012 01:30 PM

    mbarjawi's Avatar

    Thank you @Ondina for adding the project to RL2 examples.

    I am glad that I was able to contribute to this community :) (of course you get the credit for this project as I just implemented a similar idea to what you had in your robotlegs2-sketches project).

    Thank you for bringing my attention to the cleaning up process. I have added the changes to the project.

    :) I like the father - child example.

    Since we are talking about modular communications... the event dispatcher channels use events to communication between modules. How about if we want to use Signals? I haven't tried it yet... but I would think that a signal would simply be defined in the Shell context (similar to the event dispatchers that are defined there), then injected into the module that wants to listen to it.

  14. Support Staff 14 Posted by Ondina D.F. on 28 Sep, 2012 02:34 PM

    Ondina D.F.'s Avatar

    Thank you @Ondina for adding the project to RL2 examples. I am glad that I was able to contribute to this community :) (of course you get the credit for this project as I just implemented a similar idea to what you had in your robotlegs2-sketches project).

    You are more than welcome :)
    I’m really glad that my experiments have been helpful. As I said already, I won’t be able to refine my ChatModule, write documentation and upload the tests for the project anytime soon, so I think your example, being simple and clear enough, will be doing a really good job at showing how the rl2 inter-modular communication works.

    If your time permits, I suggest to add a sub-module as well (child of module one, for example) to exemplify the ModularityExtension settings (inherit, expose) that we’ve discussed previously.

    Since we are talking about modular communications... the event dispatcher channels use events to communication between modules. How about if we want to use Signals? I haven't tried it yet... but I would think that a signal would simply be defined in the Shell context (similar to the event dispatchers that are defined there), then injected into the module that wants to listen to it.

    You’re right, mapping a signal like this in the parent context:

    injector.map(InterModularSignal).asSingleton();

    should make it available to the child as well.

    Why don’t you do a version of RL2ModularCommunications with signals? ;)

    If your questions have been answered and you think we’ve solved all the problems (in the world!) you can close the thread and re-open it later, if need be.

    Cheers
    Ondina

  15. 15 Posted by mbarjawi on 28 Sep, 2012 03:32 PM

    mbarjawi's Avatar

    Thanks again, I will update the current example to have a sub-module.

    I will close this thread for now, and then once I have the signals example, I'll reopen this thread, and post a link here.

  16. mbarjawi closed this discussion on 28 Sep, 2012 03:32 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