I've always loved to play around with MVC frameworks. Since AS3 came out i used quite a bunch of them: LowRA, PureMVC, Soma, Mate, SpringAS, Parsley, Swiz and finally Robotlegs. They all have pros and cons and i could spend hours taking about why i changed from one to the other. Some offer more (and think it's good), some offer less (and think it's better)... I guess the only sure thing, is that they all share a common base: MVC.
Anyway, i love Robotlegs simplicity and this is why i use it at the moment. Even better, Stray created some helper classes to deal with modular environments (think modules as a way to split your app in small parts, not Flex modules). I knew Parsley had a very smart (and as usual quite complexe) inter-module communication system, so i wanted to give RL's implementation a try.
A first example
I created a dummy app to test it (the app is quite useless as usual...). Obviously this could have been done with a single core (read "not using the Modular implementation") or even without Robotlegs at all, but as it is the aim of the post...
You are missing some Flash content that should appear here! Perhaps your browser cannot display it, or maybe it did not initialize correctly.
Click on any square to remove it (waw!)
(Minimal comps by Keith Peters)
This app contains a Shell and 2 "child" modules. The first one (containing only the 2 buttons) is only sending external events, the second one (containing the "canvas") has a proper logic to add/remove random colored squares. It also listen for external events (coming from other modules) through a kind of gateway: a ModuleMediator. Both modules have their proper "Robotlegs wiring" system (their own MVC structure based on a "local" eventdispatcher), and both also have access to a shared ModuleDispatcher which will let them listen -and dispatch- inter-modules events.
Actually creating a modular app with RL is very straightforward: all you have to do is create your Context based on ModuleContext (rather than Context) and give it an external EventDispatcher to communicate with the other modules. My only doubts were on the wiring: i decided to create a kind of master-slave implementation where the Canvas module can really works on its own (master: without any reference to other modules) and the Input module triggers CanvasEvent (owned by the Canvas module). Still wondering if referencing an external Event class wouldn't be better...
Going further
With the first example we saw that communication between modules is easy, but what about having multiple times the same module on stage? Can they all live peacefully and still respond to external events? In the next example i built kind of the same app, but you can add and remove Canvas (Canvas being a module).
You are missing some Flash content that should appear here! Perhaps your browser cannot display it, or maybe it did not initialize correctly.
add a Canvas before adding Squares
The answer is a big yes! Modules can be added multiple times to the stage without any problem. Thanks Robotlegs for not using some locator pattern based on Statics (remember good old pureMVC time?)
This example also shows that a module can be created and destroyed without problems (just clean your own stuff and Robotlegs will clean its own things magically -eventmaps, commandmaps, etc-)
Notes
- To unmap singletons and make sure they can be GC, use SwiftSuspender beta swc. See this post on RL knowledge base for more info. Thanks Till!
- If you use the ModuleCommandMap (which make it really easy to trigger internal commands when receiving external events), just make sure that you manually unmap events when you free the Context (see CanvasModuleMediator in canvas module in my example) as the current version of the modular extension doesn't seem to do it automatically. (Thanks for your help on this Stray!)
The source of both examples are available on Github (see branch called "simple" for the first example). Robotlegs modular is also on Github as is Robotlegs itself.
Comments
Great post! I do have some questions/concerns...
I've noticed that the "InputCommand" in the input module is dispatching "CanvasEvent" events... And also, the "CanvasModule" is dispatching "ShellEvent" events. I understand if you were just doing this to quickly wire up an application, but I'm wondering, in regard to making a reusable modular app, wouldn't you want to make sure that each module DOES NOT import anything outside of its own package? Isn't it part of the point to have a module in such a state that I can simply build a new RL shell, grab my old module, pop it in and send it its own events...?
It seems as though with the way you have this example built, in order to reuse a module I built for an old app, I would require me to open it up and change the events/commands and so on and so forth in order to get it to work with a new project. Wouldn't it be best to be able to simply import the necessary classes from my module into my new shell, and then simply dispatch the modules' own events from the shell to the module? And furthermore, couldn't the shell act as sort of a 'translator' to help modules talk to one another without having to couple them? For instance: my 'InputModule' dispatches an input event which is picked up by the shell, the shell then translates this to a 'CanvasEvent' and the shell dispatches that to be picked up by the 'CanvasModule'. That way both modules could care less about each other, and if built right, I might never need to open them up again...
Maybe I'm not seeing things right, but as of now, this seems like the most logical way to use these modules... To NEVER import a single class out side of its own Module package (unless its a dependency like TweenMax or RL of course).
What do you think? I'm still trying to figure this all out, so any kind of reply is more than appreciated!
Thanks again for the awesome example! Great work!
-j
Thanks Justin!
Your concerns are legitimate. As this example is pretty useless, it's really hard to tell, but I guess it really depends on the kind of modules you're building.
If you were to build a Log module for ex. would you dispatch a LogEvent from the other modules, or a MyModuleSaySomethingEvent which would then be translated by the Shell to a LogEvent? Both would end up in a log, but i would rather use the 1st solution. The 2nd solution would mean that for all the modules i have to register a translator to LogEvent.
In some other case you would not want to couple your modules and use your "translator" approach.
Hope it helps to clarify, and thanks for your comment.
OK, sorry, I've got it.
Either you use the ModuleMediator with this syntax :
moduleCommandMap.mapEvent( "evenType", TypeCommand );
to map from there the outside module events to inner commands
or
mapRedispatchInternally( "evenType" );
to redirect the event to the inner module eventdispatcher, and then to use the commands declared in the module's context.
Makes perfect sense now !
yeah, there are multiple ways to do it: you could also use a startupCommand (in which you would inject the moduleCommandMap) or if you really want to do this in the context itself, just override the setModuleDispatcher() method to store -in the context- a reference of the ModuleCommandMap instance.
But i like to keep the module as a "normal" Robotlegs MVCS (kind of module agnostic) with the all the module-wiring stuff done in the moduleMediator.
OK, I see your point.
You see the ModuleMediator as a gateway between the modules.
The mediator is so dedicated to the view managment in robotlegs, that I was a little disturbed to use it to redirect all the inter-module communications.
I was seeing the other options :
- using a startupCommand in which the moduleCommandMap is injected
- overriding the setModuleDispatcher() method to store in the module's context a reference of the ModuleCommandMap instance
as good design.
In fact, the context act as a configuration for the whole inner module wirings.
I was wondering if the ModuleMediator, like you use it, didn't duplicate this behavior.
But I completly agree with your module agnostic point.
Thank you for all, that make great sense !
Hi,
Thanks for the example.
BTW, it seems to me that declaring the inter-modules commands inside the mediator of context view loaded module kind of a mess, ain't it ?
I'd prefere declare them in the module's context, but from there, injections of moduleDispatcher and moduleCommandMap are nto done.
Any tip to achieve this maybe ?
Greetings
Excellent blog post, thanks for taking the time to share this!
Post new comment