(Please have a look at part 1 of this walkthrough!) Source code for the following example can be found here.
So, now we have an application shell for our clock — we have the UI and we can interact with it.
ClockModel – our application data
We are dealing with time here — time is our application data, and in the MVC pattern the data belongs to the model. So let’s create one, and put it in the clock.model package:
// clock.model.ClockModel.as package clock.model { import flash.events.TimerEvent; import flash.utils.Timer; import org.robotlegs.mvcs.Actor; public class ClockModel extends Actor { private var clockTimer:Timer = new Timer(1000); public function ClockModel() { super(); this.clockTimer.addEventListener(TimerEvent.TIMER, onTimer); } public function startClock():void { trace("ClockModel.startClock"); this.clockTimer.start(); } public function stopClock():void { trace("ClockModel.stopClock"); this.clockTimer.stop(); } private function onTimer(e:TimerEvent):void { trace("ClockModel.onTimer"); // run every second (1000ms) // this is where we will broadcast the TimerEvent } } }
Just a regular model class, nothing special except that it extends the RobotLegs Actor class.
It has a timer that is setup to dispatch a TimerEvent every second (1000 ms), and it has public methods for starting and stopping the timer.
So, let’s get our app to know about this model! This is done in the AppContext class. Because we will need one, and only one, instance of our model, we map it as a singleton:
// clock.AppContect.as import clock.model.ClockModel; . . . . . override public function startup():void { trace('AppContext.startup'); // Let the framework know of our view and mediator this.mediatorMap.mapView(ClockView, ClockViewMediator); // Let the framework know of our model - as a singleton this.injector.mapSingleton(ClockModel); }
Now, the framework knows about our model, but nothing happens yet. Time for commands!
StartClockCommand
When we want things to be done in RobotLegs, we use commands. We use them to control the application, and thus the commands belong to the Controller part of the MVC triad.
Now we want to create a command for starting the model timer. We create a StartClockCommand class extended from RobotLegs Command, and we put it in the clock.controller package:
// clock.controller.StartClockCommand.as package clock.controller { import org.robotlegs.mvcs.Command; import clock.model.ClockModel; public class StartClockCommand extends Command { // [Inject] private var clockModel:ClockModel; public function StartClockCommand(clockModel:ClockModel) { super(); this.clockModel = clockModel; } override public function execute():void { trace("StartClockCommand.execute"); } } }
Our start command needs to know the ClockModel to be able to start it, and the injection framework already takes care of that. (In the AppContext we declared that whenever ClockModel is needed, the injection framework will give us the singleton instance.)
Please note that I use normal oo constuctor parameter injection instead of using [Inject] metatag (wich is the most common RobotLegs practice).
The command class’s main player is the execute() method. When the command is fired, the execute method is run. In this case we want to start our model timer by calling clockModel.startClock() method:
override public function execute():void { trace("StartClockCommand.execute"); this.clockModel.startClock(); // <----- }
So, now the framework has a way of starting the model timer. Now we have to be able to fire this command.
Could this be done from the ClockViewMediator? Well, maybe, but that would break our low coupling ambitions: The StartClockCommand knows of the ClockModel, and we don’t wont our views/mediators to know of the models — if so, we would get tight coupling, and that’s what we want to avoid!
Instead we will let our mediator dispatch an event recognized by our context command central (event bus), and let the context fire the command for us!
ClockEvent
Here’s our ClockEvent. Just an ordinary event guy, put in the clock.controller package.
We give it three static type constants: for starting and stopping, and for ticking (for later use).
We also add a time:Date field that later will act as carrier for the current time, when we later broadcast this from the model.
// clock.controller.ClockEvent.as package clock.controller { import flash.events.Event; public class ClockEvent extends Event { public static const START:String = "start"; public static const STOP:String = "stop"; public static const TICK:String = "tick"; public var time:Date; public function ClockEvent(type:String, time:Date = null, bubbles:Boolean = false, cancelable:Boolean = false) { super(type, bubbles, cancelable); this.time = time; } } }
Now we will let the AppContext know that when ever it hears a ClockEvent.START dispatched, it will fire the StartClockCommand:
// clock.AppContext.as import clock.controller.ClockEvent; import clock.controller.StartClockCommand; . . . . override public function startup():void { trace('AppContext.startup'); . . . . . // Whenever ClickEvent.START is recognized, StartClockCommand will be fired this.commandMap.mapEvent(ClockEvent.START, StartClockCommand); }
Now, let’s dispatch the ClockEvent.START when we press the Start button. We just have to add one line in the ClockViewMediator onBtnStartClick method:
// clock.view.ClockViewMediator.as import clock.controller.ClockEvent; . . . . . private function onBtnStartClick(e:MouseEvent): void { trace('ClockViewMediator.onBtnStartClick'); // Tell the framework context that we want to start the clock! this.dispatch(new ClockEvent(ClockEvent.START)); }
This is how our code structure will look right now, with model, command and event:
When run and the start button is pressed, the following will happen:
- The mediator fires a ClockEvent.START event
- The ClockEvent.START is recognized by the context, and it fires the StartClockCommand
- The StartClockCommand.execute() method calls the ClockModel.startClock() method.
- The ClockModel timer is started, and every second the onTimer callback is executed, wich traces a “ClockModel.onTimer” message to let us know that it works.
StopClockCommand
The StopClockCommand is set up exactly the same way as the StartClockCommand. I will skip over it here.
Updating the UI with the current time
One thing left: Every second, the UI should be updated to display the current time. Again, this is done through events.
We start by letting the model dispatch a ClockEvent.TIME event, carrying the actual time in the time:Date field by passing a new Date():
import clock.controller.ClockEvent; . . . . . private function onTimer(e:TimerEvent):void { trace("ClockModel.onTimer"); // dispatch a ClockEvent with the current time this.dispatch(new ClockEvent(ClockEvent.TICK, new Date())); }
The AppContext knows of our ClockModel, and also has an event bus that listens for broadcasted events, and passes them further through an eventDispatcher. In our ClockViewMediator, we now have to listen to that eventDispatcher (not to the ClockModel itself — we don’t want tight coupling!). This is done in the mediator onRegister method by adding a listener for the ClockEvent.TICK. We also add an onClockTick callback that updates the clock label in the ClockView:
// clock.view.ClockViewMediator import clock.controller.ClockEvent; . . . . . . override public function onRegister():void { trace('ClockViewMediator.onRegister'); . . . . . . // Listen for the ClockEvent.TICK passed by the framework eventDispatcher this.eventMap.mapListener(this.eventDispatcher, ClockEvent.TICK, onClockTick); } . . . . . private function onClockTick(e:ClockEvent): void { trace("ClockViewMediator.onClockTick"); this.clockView.clockLabel.text = e.time.hours + ':' + e.time.minutes + ':' + e.time.seconds; }
So, now we are finished! We have a simple clock application built in a loose coupled architectural way! Great! 🙂
Omer Hassan
August 19, 2010
Thanks for the super simple tutorial! This is what got me started with robotlegs.
cambiatablog
August 20, 2010
Great!
jaf
August 25, 2010
Great Tutorial! Keep going with how to implement Services and you’ll have a trifecta!
Ivan
September 1, 2010
Only one problem when you don’t use [Inject] tag.
As stated in the Robotlegs Internals document (excerpt):
No metadata! And, the property is available immediately. Brilliant, except that there is a bug in most versions of the Flash Player (pre 10.1) that throws a spanner in the works: the constructor argument information that we need is not available until after at least one instance of that class has been constructed. This means that the DI container has to create a dummy, throw-away instance whenever it encounters such a class for the first time. It’s not a big deal, but it’s something to be aware of – especially if your constructors perform actual work.
http://wiki.github.com/robotlegs/robotlegs-framework/robotlegs-internals
cambiatablog
September 1, 2010
Thank you for pointing this out, Ivan!
In practice, this would cause some slight memory/performance loss, I guess. One other thing worth mentioning is that the class can’t be instantiated in mxml when using construcor parameters, they just aren’t allowed…
David Jumeau
December 2, 2010
This is a great tutorial on starting off with Robotlegs! Thanks! Have you thought about another about implementing services?
cambiatablog
December 2, 2010
Thank you, David! Maybe. If so, it will focus on RESTful services using X-HTTP-Method-Override. Right now I’m delving into GWT and MVP concepts involving Guice/Gin – lots of similarities with RobotLegs (not very strange as RobotLegs is partly modelled after Guice). Maybe there will be a tutorial on that!
Dirk Verstraete
December 28, 2010
Thanks for this great (and simple) tutorial. I am using it as an example for introducing our developpers to RobotLegs. They will have fun with it (I hope).
cambiatablog
December 28, 2010
Great to hear, Dirk! Did you find it by googling or through RobotLegs site? (Just curious!)
Dirk Verstraete
December 30, 2010
It was the power of Google that brought me to your site.
Nikos
January 27, 2011
How would you call a command from a command, how would this called command look like?
thanks 🙂
cambiatablog
February 5, 2011
Hi Nikos! I’m using Aaron Hardy’s Macrobot utilities for that. Works great! Let’s you wrap one or many commands in a serial (executed “one by one”) or parallell (started all at once) commands.
Vijay
March 31, 2011
Thanks.
It a really gr8 and ideal for whose want to start Robotlegs.
Brad Swardson
September 15, 2011
Really great tutorial! Thank you for putting it together.
Is there a reason you used “this.eventMap.mapListener(” instead of “addViewListener” and “addContextListener” when catching events?