Go Tripod

Building a medium to large scale Flex App using Cairngorm (Part 2)

Sun Jun 08 2008

In Part 1 we created a new Flex project and setup the classes we that will be used to support our application architecture. In this part we will look at how we build our application around these classes.

Having set up our project in Step 1 we are ready to start building our app. Like many flex developers, I don’t generally start building the view in the default application mxml file (CairngormDemo.mxml in the example I have been using). Instead I create a new MXML Component called Main.mxml file in the View folder (often based on a VBox or Canvas, 100% wide x 100% high). From here, all my other views hang off this. The main reason for this is that the application file needs to sit in the source root, while the rest of our application sits in our namespace folders. This avoids having code here.

There are various ways to implement the different application states. One approach which is popular, is to have a view state (within Main.mxml) for each top level state of the application, so for example I may have a default logged out state, a logged in state and possibly and admin state for example.

We need to ensure that we have nested our Main.mxml view into the application. While we are editing our application file, I place the Caringorm controller (App Controller we created in Part 1 here too). For purpose of example, my CairngormDemo.mxml file looks like this:

I may also add an mx:style tag referencing an external style sheet here but other than that nothing else goes here.

A model for session and state

I like to create a session model to hold session state data such as view states and depending on our apps perhaps the currently logged in user etc.

I create this model, as I would any other model (so it will be a good example). I firstly create a folder for this session model under the model directory (in this case called session) and create my new simple ActionScript class called SessionModel in that folder that will hold my model data.

As we are going to bind to data in this model directly from our mxml files, so we need to use the [Bindable] Meta tag.

For now we will add a property to hold the current view state for Main.mxml and store the name of each state as static properties on the model. As our app grows we will add other view states that need we want to make globally accessible here.

package uk.co.clockobj.cairngormdemo.model.session
{
	[Bindable]
	public class SessionModel
	{
		public function SessionModel()
		{
		}

		public static const APP_STATE_LOGGED_OUT : String = "";
		public static const APP_STATE_LOGGED_IN : String = "appStateLoggedIn";
		public static const APP_STATE_ADMIN : String = "appStateAdmin";

		public var appState : String = APP_STATE_LOGGED_OUT;

	}
}

We now want to make this locatable via our Model Locator. We can make the following modifications to the ModelLocater we created in Part 1 as as follows:

public function ModelLocator()
		{
			if ( instance != null )
			{
				throw( new Error( "Singleton class has already been instantiated" ) );
			} else
			{
				// Instantiate the model when the Locator is Instantiated
				session = new SessionModel();
			}
		}

		public var session : SessionModel;

Once we have done this we can add our bindings to Main.mxml’s currentState property using Flex’s binding mechanism, as in the following example:

<?xml version="1.0" encoding="utf-8"?>
<mx:VBox xmlns:mx="http://www.adobe.com/2006/mxml" width="100%" height="100%"
	currentState="{ ModelLocator.getInstance().session.appState }">
	<mx:states>
		<mx:State name="appStateLoggedIn">
			<!-- Changes to mxml when logged in -->
		</mx:State>
		<mx:State name="appStateAdmin">
			<!-- Changes to mxml when admin logged in -->
		</mx:State>
	</mx:states>
	<mx:Script>
		<![CDATA[
			import uk.co.clockobj.cairngormdemo.model.ModelLocator;
		]]>
	</mx:Script>

	<!-- Default view -->

</mx:VBox>

Once setup, we can now change the application’s state by doing something like this:

ModelLocator.getInstance().session.appState = SessionModel.APP_STATE_LOGGED_IN;

However, RESIST the temptation to do this all over the place. We want to maintain a convention and as this is an MVC framework, the controller should manage these state changes. Instead to change application state we will dispatch a CairngormEvent. (We will look at dispatching and handling Cairngorm events in Part 3)

More on Models and Data Transfer Objects

The Cairngorm guide by Steven Webster suggests storing Value Objects, or Data Transfer Objects, directly in the ModelLocator and binding to these directly. There are two schools of though here and your approach will depend on your project.

One is to do this and store the DTOs directly in the model, the other is to have separate model classes. The advantage of the latter is obviously less code, however by creating separate model classes to the DTOs may seem like duplication of work, you have the added advantage of being able to add business logic into your models, for example validation, conversions, etc. Secondly if the DTOs signatures change, you don’t have to re-factor your entire app changing all your bindings. You need to only to this in the translation methods between your DTOs and model. You also overcome an issue I encountered recently when returning remote objects received from a backend Java AMF server where you have to ensure all the data in cast to the correct DTOs (See this article) as the translation process overcomes this.

An example, if we had to work with product data, I would have a product model which held product items. I would build a method into my model to translate DTOs into the model and back.

Note: if you are using soap / xml you could pass in the untyped object or xml to build the class from.

With the following DTO:

package uk.co.clockobj.cairngormdemo.model.dto
{
	[Bindable]
	[RemoteClass(alias="namespace.to.some.server.dto")]
	public class ProductDTO
	{

		public var id				: int;
		public var price 			: Number;
		public var description		: String;

		public function ProductDTO()
		{
		}

	}
}

Our product items (stored in our product model) would take an optional ProductDTO class passed to the constructor to build itself from. As an example, I have also added simple business logic to ensure the price is never negative. In a future article i want to show how validation logic can be put here (Which to me seems a more appropriate place than the view):

package uk.co.clockobj.cairngormdemo.model.product
{
	import uk.co.clockobj.cairngormdemo.model.dto.ProductDTO;

	[Bindable]
	public class Product
	{
		public var id			: int;

		private var _price 		: Number;

		public var description	: String;

		public function get price() : Number
		{
			return _price;
		}

		public function set price( value : Number ) : void
		{
			if ( value < 0 )
				value = 0;
			_price = value;
		}

		public function Product( productDTO : ProductDTO = null )
		{
			if ( productDTO != null )
			{
				this.price = productDTO.price;
				this.description = productDTO.description;
			}
		}

	}
}

Finally we create our product model, as we did for our session model, but with a dto translation function (here implemented via a property setter / getter):

package uk.co.clockobj.cairngormdemo.model.product
{
	import mx.collections.ArrayCollection;

	import uk.co.clockobj.cairngormdemo.model.dto.ProductDTO;

	[Bindable]
	public class ProductModel
	{
		public var products : ArrayCollection;

		public function ProductModel()
		{
			products = new ArrayCollection()
		}

		public function set dtos( value : ArrayCollection ) : void
		{
			for each ( var pdto : ProductDTO in value )
			{
				products.addItem( new Product( pdto ) );
			}
		}

		public function get dtos() : ArrayCollection
		{
			var dtos : ArrayCollection;
			for each ( var p : Product in products )
			{
				var pdto : ProductDTO = new ProductDTO()
				pdto.id = p.id;
				pdto.description = p.description;
				pdto.price = p.price;
				dtos.addItem( pdto );
			}
			return dtos;
		}
	}
}

We would also need to put this product model into our model locator, exactly as we did for the session model.

Binding

We can now bind to our product data from throughout our application. We would do this as we bound the currentState property of Main.mxml. You can obviously use this approach when binding to editable controls (such as form fields) but, depending on the project, one thing I do is setup a two way binding (I have a custom component that uses the BindingUtils.bindProperty() method to bind the model to the form and the form back to the model). Again I hope to add an article about this in the future.

In Part 3 I will look at how we will make use of the controller, use commands to handle events, chain together a series of commands using Cairngorm’s SequenceCommand and also create custom events that extend CairngormEvents.

Contents:

Part 1 – Setting up a project for Cairngorm
Part 2 – The Model
Part 3 – Commands
Part 4 – Services
Cairngorm Demo Source Files

Got an idea for a project?

Need a website? Web-enabled software to streamline your business? Just some advice?