Inversion of Control and Dependency Injection with Flex using the Parsley Application Framework – Part 2

This is the second part in a series exploring Inversion of Control and Dependency Injection containers available for Flex. In the first part of the series we discussed what IoC and DI are and how they can benefit our applications. In this part, we will explore the Parsley IoC Container through a simple image gallery application.

Here’s the source on github…

Get Adobe Flash player

The above is a simple image gallery that is grabbing the top 100 ‘interestingness’ images from Flickr. It is built with Flex 4 and uses a mix of halo components (the images) and spark components for layout. Parsley in its current state does NOT support the Flex 4.0.0 SDK. It IS however opensource, and only required a single change to function in Flex 4. Namely, there is a call to Application.application which has been ‘deprecated’ (and by that they mean completely broken) and replaced by FlexGlobals.topLevelApplication. The modified SWC files are included in the source repository for the project. The modified swc files WILL NOT function with Flex 3.*.*

Less talk. More Code.

One aspect of an IoC/DI container that is consistent across frameworks is the need to bootstrap the configuration. Parsley provides several ways to accomplish this.

from the docs:

Configuring and initializing the Parsley Framework usually consists of the following steps:

Step 1: Telling the IOC Container which classes it should manage. This can be done with MXML, XML files or in ActionScript. All three mechanisms will be described in the following sections.

Step 2: Configure the container services like Dependency Injection or Messaging for each individual class. This can be done with the mechanism you had chosen for step 1 (e.g. with MXML or XML configuration tags) or – in most cases – conveniently with AS3 Metadata Tags right within the classes themselves.

Step 3: Initialize the IOC Container (usually on application startup). In Parsley 2 this is a one-liner in most cases. See the sections below for examples.

In the image gallery sample step one is accomplished in Beans.mxml. Beans is, I assume, a throwback to Java that I don’t fully understand, but it seems to be a popular convention and I’m not here to rock the boat.

Beans.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
<?xml version="1.0" encoding="utf-8"?>
<!--
	Inversion of Control/Dependency Injection Using Parsley
	Image Gallery
 
	Any portion of this demonstration may be reused for any purpose where not
	licensed by another party restricting such use. Please leave the credits intact.
 
	Joel Hooks
	http://joelhooks.com
	joelhooks@gmail.com
-->
<mx:Object
    xmlns:mx="http://www.adobe.com/2006/mxml"
    xmlns="http://www.spicefactory.org/parsley"
    xmlns:models="com.joelhooks.parsley.example.models.*"
    xmlns:controllers="com.joelhooks.parsley.example.controllers.*"
    xmlns:presentation="com.joelhooks.parsley.example.models.presentation.*">
 
    <mx:Script>
    	<![CDATA[
    		import com.joelhooks.parsley.example.delegates.FlickrImageServiceDelegate;
    	]]>
    </mx:Script>
 
    <!-- Service Delegate -->
    <object id="photoServiceDelegate" type="{FlickrImageServiceDelegate}">
    	<property name="apiKey" value="YOUR_KEY_HERE"/>
    	<property name="secret" value="YOUR_SECRET_HERE"/>
    </object>
 
    <!-- Model -->
    <presentation:FlickrGalleryPresentationModel id="galleryPresentationModel"/>
 
    <!-- Actions -->
    <controllers:RetrieveGalleryAction />
    <controllers:DisplayGalleryAction />
    <controllers:SelectImageAction />
 
</mx:Object>

The application has several actors that Parsley needs to be aware of, and this is where you let the container know what they are. In most cases we can simple use MXML notation to include our classes in the configuration. The photo service, in this case Flickr, needs additional configuration so it is added a bit differently utilizing the Parsley Object and Property tags. When using this method the class needs to be imported as above to provide a reference. In addition to the service the presentation model is also added to the configuration, as well as several actions that will react to various events. The Model and Controller of the MVC triad are what we are configuring here. The View is handled a bit differently.

The configuration is loaded in the preinitialize handle of the main application MXML.

ParsleyGallery.mxml:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
<?xml version="1.0" encoding="utf-8"?>
<!--
	Inversion of Control/Dependency Injection Using Parsley
	Image Gallery
 
	Any portion of this demonstration may be reused for any purpose where not
	licensed by another party restricting such use. Please leave the credits intact.
 
	Joel Hooks
	http://joelhooks.com
	joelhooks@gmail.com
-->
<s:Application
	xmlns:fx="http://ns.adobe.com/mxml/2009"
	preinitialize="onPreInitialize()"
	xmlns:s="library://ns.adobe.com/flex/spark"
	xmlns:mx="library://ns.adobe.com/flex/halo"
	minWidth="1024" minHeight="768"
	xmlns:tag="org.spicefactory.parsley.flex.tag.*"
	xmlns:spicefactory="http://www.spicefactory.org/parsley/flex"
	addedToStage="this.addedToStageHandler(event)"
	applicationComplete="trace('application complete');"
	xmlns:views="com.joelhooks.parsley.example.views.*"
	xmlns:delegates="com.joelhooks.parsley.example.delegates.*">
	<s:layout>
		<s:VerticalLayout/>
	</s:layout>
	<fx:Script>
		<![CDATA[
			import com.joelhooks.parsley.example.delegates.FlickrImageServiceDelegate;
			import mx.controls.Button;
			import org.spicefactory.lib.reflect.types.Void;
			import org.spicefactory.lib.logging.Logger;
			import mx.logging.LogEventLevel;
			import com.joelhooks.parsley.example.events.GalleryImageServiceEvent;
			import org.spicefactory.parsley.flex.FlexContextBuilder;
 
			protected function onPreInitialize():void
			{
				this.logTarget.level = LogEventLevel.ALL;
				FlexContextBuilder.build(Beans);
			}
 
			protected function addedToStageHandler(event:Event):void
			{
				//Let the container know that this component is ready to be added
				//to the view context
				dispatchEvent(new Event('configureIOC', true));
			}
 
		]]>
	</fx:Script>
	<fx:Declarations>
		<mx:TraceTarget id="logTarget"/>
	</fx:Declarations>
	<views:GalleryView/>
</s:Application>

We want to make sure that the Parsley container is configured at the first opportunity, and preinitialize provides this hook. After the application has been added to the stage, we need to dispatch a message to the container so that it can be added to the ViewContext allowing the visual components to interact with the container. This is done by dispatching a bubbling ‘configureIOC’ event in the addedtoStage handler.

This needs to be done for all viewComponents that require access to the container as you will see in the child GalleryView. At this point, the application is fully configured within the IoC Container. Our actors that have been configured in Beans.mxml have had their appropriate dependencies injected, and our Actions are ready to receive messages from the other objects in the container.

DisplayGalleryAction.as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
/*
	Inversion of Control/Dependency Injection Using Parsley
	Image Gallery
 
	Any portion of this demonstration may be reused for any purpose where not
	licensed by another party restricting such use. Please leave the credits intact.
 
	Joel Hooks
	http://joelhooks.com
	joelhooks@gmail.com
*/
package com.joelhooks.parsley.example.controllers
{
	import com.joelhooks.parsley.example.events.GalleryEvent;
	import com.joelhooks.parsley.example.events.GalleryImageEvent;
	import com.joelhooks.parsley.example.models.presentation.IGalleryPresentationModel;
	import com.joelhooks.parsley.example.models.vo.GalleryImage;
 
	import flash.events.EventDispatcher;
 
	[Event(name="selectGalleryImage", type="com.joelhooks.parsley.example.events.GalleryImageEvent")]
	[ManagedEvents("selectGalleryImage")]
	public class DisplayGalleryAction extends EventDispatcher
	{
		[Inject(id="galleryPresentationModel")]
		public var galleryPresentationModel:IGalleryPresentationModel;
 
		public function DisplayGalleryAction()
		{
		}
 
		[MessageHandler(selector="galleryLoaded")]
		public function handleGalleryLoaded(event:GalleryEvent):void
		{
			this.galleryPresentationModel.gallery = event.gallery
			trace(event.gallery.photos.length);
			this.dispatchEvent(new GalleryImageEvent(GalleryImageEvent.SELECT_GALLERY_IMAGE,
				event.gallery.photos[0] as GalleryImage));
		}
	}
}

The DisplayGalleryAction class is wired to listen for the system message (event) galleryLoaded. It has no interest in where this message originates, and simply responds as instructed when the message is received. The handleGalleryLoaded method is designated as the MessageHandler for the galleryLoaded message. The message is an actionscript event that has been intercepted by the container and routed to interested parties. You will notice that the handleGalleryLoaded method is constructed exactly like any other Actionscript event handler (with the addition of the Parsley metadata). In this case it receives the custom GalleryEvent that carries some information.

Additionally, DisplayGalleryAction has been injected with our FlickrGalleryPresentationModel from Beans.mxml. FlickrGalleryPresentationModel implements IGalleryPresentationModel, and Parsley will inject any class that implements this interface and has the appropriate ID ‘galleryPresentationModel’.

DisplayGalleryAction also dispatches the ManagedEvent ‘selectGalleryImage’. Any container actor that dispatches events that need to be distributed to other interested parties need to let the container know. The class is annotated with AS3 metadata designating which ManagedEvent the class will be sending to the container.

It is important to note that Parsley does not impose any strict patterns on your application. Any of the actors can be wired to received dependency injections and send/receive messages from the container. It is up to you to architect your application in the manner most appropriate to your environment.

DisplayGalleryAction receives Messages from FlickrImageServiceDelegate. it could, of course, receive its Messages from any class that is managed by the container, but in this case it is only interested in the data loaded by the delegate.

FlickrImageServiceDelegate.as:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
/*
	Inversion of Control/Dependency Injection Using Parsley
	Image Gallery
 
	Any portion of this demonstration may be reused for any purpose where not
	licensed by another party restricting such use. Please leave the credits intact.
 
	Joel Hooks
	http://joelhooks.com
	joelhooks@gmail.com
*/
package com.joelhooks.parsley.example.delegates
{
	import com.adobe.webapis.flickr.FlickrService;
	import com.adobe.webapis.flickr.Photo;
	import com.adobe.webapis.flickr.events.FlickrResultEvent;
	import com.adobe.webapis.flickr.methodgroups.Photos;
	import com.adobe.webapis.flickr.methodgroups.helpers.PhotoSearchParams;
	import com.joelhooks.parsley.example.events.GalleryEvent;
	import com.joelhooks.parsley.example.events.GalleryImageServiceEvent;
	import com.joelhooks.parsley.example.models.vo.Gallery;
	import com.joelhooks.parsley.example.models.vo.GalleryImage;
 
	import flash.events.Event;
	import flash.events.EventDispatcher;
 
	import mx.collections.ArrayCollection;
 
	[Event(name="serviceReady", type="com.joelhooks.parsley.example.events.GalleryImageServiceEvent")]
	[Event(name="galleryLoaded",type="com.joelhooks.parsley.example.events.GalleryEvent")]
	[ManagedEvents("galleryLoaded,serviceReady")]
	public class FlickrImageServiceDelegate extends EventDispatcher implements IGalleryImageServiceDelegate
	{
		private var service:FlickrService;
		private var photos:Photos;
 
		public var apiKey:String;
		public var secret:String;
		public var NSID:String;
 
		public function FlickrImageServiceDelegate()
		{
		}
 
		[PostConstruct]
		public function init():void
		{
			trace("Configuring Service");
			this.service = new FlickrService(this.apiKey);
			this.photos = new Photos(this.service);
			this.dispatchEvent(new GalleryImageServiceEvent(GalleryImageServiceEvent.SERVICE_READY));
		}
 
		public function loadGallery():void
		{
			service.addEventListener(FlickrResultEvent.INTERESTINGNESS_GET_LIST, handleSearchResult);
			service.interestingness.getList()
		}
 
		protected function handleSearchResult(event:FlickrResultEvent):void
		{
			this.processFlickrPhotoResults(event.data.photos.photos);
		}
 
		protected function processFlickrPhotoResults(results:Array):void
		{
			var gallery:Gallery = new Gallery();
			gallery.photos = new ArrayCollection();
			for each(var flickrPhoto:Photo in results)
			{
				var photo:GalleryImage = new GalleryImage()
				var baseURL:String = 'http://farm' + flickrPhoto.farmId + '.static.flickr.com/' + flickrPhoto.server + '/' + flickrPhoto.id + '_' + flickrPhoto.secret;
				photo.thumbURL = baseURL + '_s.jpg';
				photo.URL = baseURL + '.jpg';
				gallery.photos.addItem( photo );
			}
 
			this.dispatchEvent(new GalleryEvent(GalleryEvent.GALLERY_LOADED, gallery));
		}
	}
}

As we’ve seen, the FlickrImageServiceDelegate is an implementation of the IGalleryImageServiceDelegate interface. This allows us to program to an interface instead of an implementation for that loose coupling goodness we all crave. This will come in very handy when we want to modify the application to handle multiple services or build mock objects for the purpose of unit testing (the subject of part 3).

As with the DisplayGalleryAction class, FlickrImageServiceDelegate is configured to send ManagedEvent messages to the container. One of these events is ‘serviceReady’. FlickrImageServiceDelegate utilizes the [PostConstruct] annotation on its init method. When this class was configured, we injected properties (the apiKey and secret), and the service cannot be configured without these. Using [PostConstruct] the container is ordered to run the annotated method after the class has been configured thus ensuring our properties have been injected as expected. FlickrImageServiceDelegate then dispatches the ‘serviceReady’ Message so that the actors waiting to get to work can do what they need to do.

The GalleryView is responsible for displaying the data and accepting user interaction.

GalleryView.mxml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
<?xml version="1.0" encoding="utf-8"?>
<!--
	Inversion of Control/Dependency Injection Using Parsley
	Image Gallery
 
	Any portion of this demonstration may be reused for any purpose where not
	licensed by another party restricting such use. Please leave the credits intact.
 
	Joel Hooks
	http://joelhooks.com
	joelhooks@gmail.com
-->
<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/halo"
	width="500" height="600"
	addedToStage="dispatchEvent(new Event('configureIOC', true));" creationComplete="creationCompleteHandler(event)" xmlns:display="com.joelhooks.display.*">
 
	<s:layout>
		<s:BasicLayout/>
	</s:layout>
	<fx:Metadata>
		[Event(name="selectGalleryImage", type="com.joelhooks.parsley.example.events.GalleryImageEvent")]
		[ManagedEvents("selectGalleryImage")]
	</fx:Metadata>
 
	<fx:Script>
		<![CDATA[
			import org.spicefactory.lib.reflect.types.Void;
			import mx.events.FlexEvent;
			import com.joelhooks.parsley.example.events.GalleryImageEvent;
			import com.joelhooks.parsley.example.models.presentation.IGalleryPresentationModel;
			import com.joelhooks.parsley.example.views.renderers.GalleryImageThumbnailItemRenderer;
			import mx.collections.ArrayCollection;
 
			[Bindable]
			[Inject(id="galleryPresentationModel")]
			public var model:IGalleryPresentationModel;
 
 
			protected function creationCompleteHandler(event:FlexEvent):void
			{
				this.dgThumbnails.addEventListener( GalleryImageEvent.SELECT_GALLERY_IMAGE, handleSelectImage);
			}
 
			protected function handleSelectImage(event:GalleryImageEvent):void
			{
				this.dispatchEvent(event);
			}
 
		]]>
	</fx:Script>
	<s:Group width="100%" height="100%">
		<s:Rect width="100%" height="100%">
			<s:fill>
				<mx:SolidColor color="#000000"/>
			</s:fill>
		</s:Rect>
	</s:Group>
	<s:Group width="100%"  height="100%">
		<s:layout>
			<s:VerticalLayout gap="0"/>
		</s:layout>
		<display:ImageSwap id="image" width="100%" height="100%" source="{this.model.selectedImage.URL}" />
 
		<s:DataGroup
			id="dgThumbnails"
			clipAndEnableScrolling="true"
			width="500"
			height="85" mouseEnabled="{!image.changingImages}" mouseChildren="{!image.changingImages}"
			dataProvider="{this.model.gallery.photos}"
			itemRenderer="com.joelhooks.parsley.example.views.renderers.GalleryImageThumbnailItemRenderer">
			<s:layout>
				<s:HorizontalLayout gap="0"/>
			</s:layout>
		</s:DataGroup>
		<s:HScrollBar viewport="{this.dgThumbnails}" width="500"/>
	</s:Group>
</s:Group>

As with the main application MXML, the GalleryView sends a ‘configureIOC’ event to the container when it is added to the stage. This allows it to participate in the Messaging system and have its dependencies injected. GalleryView dispatches one message, ‘selectGalleryImage’, when a user clicks a thumbnail. It is also injected with a reference to the applications IGalleryPresentationModel (FlickrGalleryPresentationModel) where it accesses the array collection containing the objects presented in the thumbnail DataGroup.

There are two more Actions, RetreiveGalleryAction and SelectImageAction that function as their names imply. In addition the project has several custom events that are used for messaging. I’m not going to detail these as it would be repetitive, but it is worth noting that this only scratches the surface of the features that are available in Parsley. The docs are extensive, and the API is extremely robust. Typically you won’t ever need to access the API directly after you make the call in the preinitialize method to bootstrap the container. After that it does its work silently in the background.

So I think…

Parsley could be considered a ‘magical’ black box framework. This is the trade off you get removing all of the boiler plate code you see in other frameworks. Looking at the extent of classes and libraries that make up Parsley is intimidating. You are placing a lot of trust in a complicated framework and have to hope that trust isn’t misplaced. There is something to be said for a simple ‘non-magical’ framework like PureMVC. Yes, it comes at the cost of boiler plate code, but comprehension is less of a concern.

The biggest challenge Parsley presents to learning the framework is that code examples are scarce (at best) outside of the out of context examples in the (excellent otherwise) documentation and a couple blog posts. Researching the framework, I actually found myself referring to SWiZ examples to understand some of the applied concepts. This is interesting in that the SWiZ examples really do carry over, as the frameworks have significant overlap in scope and functionality. There is a thread on the SWiZ Google Group that goes into this, and I was surprised to see the complete lack of knowledge even to the existence of Parsley. I’ve been aware of the framework since early 2008, but didn’t take time to get my head around it. It really is perplexing as to why there aren’t more examples floating around, as Parsley is very mature and robust at this stage in its development. That is something that I really love about PureMVC. Cliff has done such an excellent job centralizing the examples and framework tutorials with the manifold project. The effort it takes to maintain are huge, so I understand that most don’t have the time or inclination to do so, but it is much appreciated to be sure.

In Part 3 we will go into swapping out the service and get a little unit testing coverage on the application.

I got a little distracted with Robotlegs and wrote part 3 of this tutorial with that framework instead. Parsley is awesome, but Robotlegs suits my needs and makes me smile.
  • http://twitter.com/velobr Marvin Froeder

    Is this Parsley a Swiz fork?!

    I'm currently using Swiz and found Parsley very similar.

    VELO

  • http://joelhooks.com Joel Hooks

    No, they were developed completely independently of one another.

  • http://twitter.com/velobr Marvin Froeder

    Is this Parsley a Swiz fork?!

    I'm currently using Swiz and found Parsley very similar.

    VELO

  • http://joelhooks.com Joel Hooks

    No, they were developed completely independently of one another.

  • dbuzz2000

    I got that:
    TypeError: Error #1010: A term is undefined and has no properties.
    at com.joelhooks.parsley.example.delegates::FlickrImageServiceDelegate/handleSearchResult()[C:_projects.examlpesex_parsleyflickr_IoC_DIsrccomjoelhooksparsleyexampledelegatesFlickrImageServiceDelegate.as:62]

  • dbuzz2000

    I got that:
    TypeError: Error #1010: A term is undefined and has no properties.
    at com.joelhooks.parsley.example.delegates::FlickrImageServiceDelegate/handleSearchResult()[C:_projects.examlpesex_parsleyflickr_IoC_DIsrccomjoelhooksparsleyexampledelegatesFlickrImageServiceDelegate.as:62]

  • Theguest

    Great Article, I also found this important fact about parsleys silent failure issue http://stageaction.wordpress.com/2011/11/17/flex-parsley-silent-failure/