RobotLegs clock example walkthrough 1 – views, mediators and context

Posted on August 7, 2010

1


I’ve been trying to get the grips on dependency injection in AS3 for a while. Robotlegs MVCS framework (http://robotlegs.org) is causing the most buzz in the community now, but it seemed to be a too big step for me: not only dependency injection, but also at the same time mediators classes, commands, scary metatags…

Well, after fiddling some hours with more DI-specific libraries like SmartyPants (http://smartypants.expantra.net/) and Dawn (http://wiki.github.com/sammyt/dawn/), I’m now back to RobotLegs, and I think I like it! 🙂 So, here’s a walkthroug of an MVC clock example. I’ts inspired by the Vizio clock app (http://blog.vizio360.co.uk/2010/04/robotlegs-example-clock-app/), but also takes some UI action with start/stop buttons.

Source code for this walkthrough can be found here.

So, let’s first take a look at the app we are going to build. It’s a very basic clock example with one label showing the current time, and two buttons – one for start and one for stop:

First, we create a new FlashBuilder example. I’m using flex sdk 4.0 here. We need to download RobotLegs library from robotlegs.org and SwiftSuspenders (upon wich RobotLegs DI is built) from http://github.com/tschneidereit/SwiftSuspenders. The versions used in this example are RL 1.1.2 and SS 1.5.1.

So, here’s what my project setup looks like:

I’m using the source-code libraries (org. packages) in this project, not the compiled .swc versions. I’ve also created a clock. package where we are going to put our classes.

ClockView – the user interface

First, we are going to create our view. It’s going to be just the UI elements, a label and two buttons. The code below is saved as ClockView.mxml in the clock.view package:

// clock.view.ClockView.mxml
<?xml version="1.0" encoding="utf-8"?>
<s:Group xmlns:fx="http://ns.adobe.com/mxml/2009"
		 xmlns:s="library://ns.adobe.com/flex/spark"
		 xmlns:mx="library://ns.adobe.com/flex/mx"
		 x="20"
		 y="20">
	<s:layout>
		<s:HorizontalLayout/>
	</s:layout>
	<s:Label id="clockLabel"
			 text="00:00:00"
			 width="80"
			 paddingTop="4"
			 paddingLeft="4"/>
	<s:Button label="Start" id="btnStart"/>
	<s:Button label="Stop" id="btnStop"/>
</s:Group>

Please note that this is it – no events fired in here. All the action is taken care of in the mediator (see below).

ClockViewMediator.as

The mediator will take care of the user actions, listen for events from other parts of the app and update the interface if necessary.
The mediator is going to depend on ClockView – therefore we have to instantiate the view, and we are going to let dependency injection handle this for us. This can be done in two ways:

1. Using [Inject] metatag — this is the common RobotLegs way of doing it:

package clock.view {
	import org.robotlegs.mvcs.Mediator;

	public class ClockViewMediator extends Mediator {

		[Inject] // Injection using [Inject] metatag
		private var clockView:ClockView;

		public function ClockViewMediator() {
			super();
		}
	}
}

2. Using constructor parameters — I prefer this standard object oriented way of doing it, because the classes can be reused in a non-RobotLegs context. And, yes, I do think that metatags are scary! 🙂

package clock.view {
	import org.robotlegs.mvcs.Mediator;

	public class ClockViewMediator extends Mediator {

		private var clockView:ClockView;
// OO-standard injection using constructor parameter
		public function ClockViewMediator(clockView:ClockView) {
			super();
			this.clockView = clockView;
		}
	}
}

It makes no difference — RobotLegs takes care of the instatiation in both cases.

Please note that the ClockMediator extends the RobotLegs framework Mediator class. Because of it’s tight relation the ClockView, it’s put in the clock.view package.

We’re going to override onRegister method, and trace a simple message so that we soon will be able to see that the mediator is working. We will also prepare a trace for the clockView instance, so we can see that it’s there, brought to life by injection:

		override public function onRegister():void {
			trace('ClockViewMediator.onRegister');
			trace('clockView: ', clockView);
		}

But hold it — we haven’t yet told the injection framework that our mediator should know about our view! Over to the context!

AppContext.se — where things are wired together

The ClockViewMediator doesn’t have any connection to ClockView yet, it’s just prepared to be able to! The context let us specify those connections — this is where things are wired together.

So let’s create a context class named AppContext (inherited from the framework Context class) and put it in the clock package.

The overridden startup() method is where the magic will happens, so we prepare this one also, and do some simple tracing so that we can see that it works:

package clock {
	import flash.display.DisplayObjectContainer;
	import org.robotlegs.mvcs.Context;

	public class AppContext extends Context {
		public function AppContext(contextView:DisplayObjectContainer = null, autoStartup:Boolean = true) {
			super(contextView, autoStartup);
		}
		override public function startup():void {
			trace('AppContext.startup()');
		}
	}
}

So, now we have a view, a mediator and a context. We can test our view layout and our context by putting them into our main application mxml:

<!-- Main.mxml - main application -->
<?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"
			   xmlns:mx="library://ns.adobe.com/flex/mx"
			   minWidth="955"
			   minHeight="600" xmlns:clock="clock.*" xmlns:view="clock.view.*">
	<fx:Declarations>
		<clock:AppContext contextView="{this}" />
	</fx:Declarations>
	<view:ClockView />
</s:Application>

Please note that our context class, AppContext, has to know about our main application. That’s why we bind the contextView parameter like this: contextView=”{this}”.

When testrun, you should see the user interface. You should also see a trace out of ‘AppContext.startup’, verifying that the context is working. But we still have to get some life into our mediator!
This is done in our AppContext class startup method by mapping our ClockView to the ClockViewMediator, using the mediatorMap.mapView method:

	import clock.view.ClockView;
	import clock.view.ClockViewMediator;
        . . . . .
		override public function startup():void {
			trace('AppContext.startup');
			// Let the framework know about the connection between wiew and mediator
			this.mediatorMap.mapView(ClockView, ClockViewMediator);
		}

We’ve made the connection, and as soon as the mediator needs an instance of the view, then the injection framework will supply it!

When run once more, you should see three traces, one for the AppContext startup, one for the ClockViewMediator registration, and an reference to the clockView instance.

User interface action

One step left for this first part of this walkthrough – user interface actions. The mediator now holds a reference to the view, so we can start to interact with it. We will here set upp listeneres for button clicks.
The RobotLegs framework knows of our view and its interface elements. It also keeps track of events through its event map, so we can trap our button clicks like this:

// clock.view.ClockViewMediator.as
import flash.events.MouseEvent;
 . . . . .
 override public function onRegister():void {
 trace('ClockViewMediator.onRegister');
 trace('clockView: ', clockView);
 // The event map lets us listen for events broadcasted by view elements
 this.eventMap.mapListener(clockView.btnStart, MouseEvent.CLICK, onBtnStartClick);
 this.eventMap.mapListener(clockView.btnStop, MouseEvent.CLICK, onBtnStopClick);
 }
 private function onBtnStartClick(e:MouseEvent): void {
 trace('ClockViewMediator.onBtnStartClick');
 }
 private function onBtnStopClick(e:MouseEvent): void {
 trace('ClockViewMediator.onBtnStopClick');
 }

When run and buttons clicked, there should be traces indicating that the clicks are handled. Nothing fancy, but the ui foundations for our application are there!

Continue to part 2 of this walkthrough!

Advertisements