RobotLegs clock example walkthrough 2 – models and commands

Posted on August 7, 2010

14


(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:

  1. The mediator fires a ClockEvent.START event
  2. The ClockEvent.START is recognized by the context, and it fires the StartClockCommand
  3. The StartClockCommand.execute() method calls the ClockModel.startClock() method.
  4. 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! 🙂