Alex Uhlmann: Cairngorm Archives

February 12, 2007

Creating a Popup in a Cairngorm Architecture

Often I see this question coming up:

"How should I best create a popup in my Cairngorm application?"

I think of creating a popup control as something only views should be concerned about. It’s like creating other view components, managing states of view components or effects. Therefore this question isn't just about creating popups, instead it's about how to invoke methods that belong to the view. (BTW: I often hear from our User Experience team that they try to prevent popups where possible)

First and foremost, I’d try to let your views directly call a method that dispatches a popup.

However, most of the use cases that came up when questions like the above have been asked are slightly more complex. Users for example want to create a popup in a completely different part of the application. Or they receive an asynchronous remote service response, only want to react to the user with a popup at that point in time and work in a context where no view references are available, such as a typical Cairngorm Command.

Some users have created the code needed for creating a popup directly in the result method of a command or in a model object that’s been called by the result method of a command.

Sticking to my above rule; that only views should be concerned about popups, I think both solutions are not the optimal approach. A command should just be an encapsulated request to your model and a model itself should also not be concerned about any UI behavior. I’d argue we need to tell the model about the state change that has just occurred in your application, but in a model context. For example you might set a loggedIn property on a Login model object to true in order to signal that the login process was successful after a positive remote service response.

So, how can our views react to that state change in our model?

In a typical Cairngorm application, we may bind UI controls to properties of our model objects. But for creating a Popup control, there’s no UI component where we can bind to. How do we best let a view invoke a view related method after a certain state change in our model occurred?

Let’s take a step by step approach using a very simple Cairngorm application, that retrieves a list of employees and displays them in a popup after successful retrieval.

Please do ask your UX team if something like this is really a good idea in a real world application. For the sake of this example we don’t care about the user experience here!

This step by step tutorial will walk you through the relevant parts. You can see and download (via right click > View Source) the complete application here. Let's go:

Dispatch a Cairngorm Event.

Let the view dispatch a user gesture via a Cairngorm Event in order to trigger the remote service:

On a mx:Button’s click event, the getEmployees method dispatches the Cairngorm event:

Excerpt from GetEmployeesCommand.as:

private function getEmployees() : void
{
  	var event : GetEmployeesEvent = new GetEmployeesEvent();
  	event.employees = model.employees;
  	CairngormEventDispatcher.getInstance().dispatchEvent( event );
}

Call and handle the remote service and modify your model.

The handling GetEmployeesCommand’s execute method calls a server side method. Its result method modifies the model. Here, it retrieves the model object Employeee via the ModelLocator.

Excerpt from GetEmployeesCommand.as:

private var employees : Employees;
		
public function execute( event : CairngormEvent ) : void
{
	employees = GetEmployeesEvent( event ).employees;
	employees.hasEmployees = false;
			
	var delegate : EmployeeDelegate = new EmployeeDelegate( this );
	delegate.getEmployees();
}
		
public function result( event : Object ) : void
{
	employees.employees = IList( event.result );
	employees.hasEmployees = true;
}

Employees.as:

package com.adobe.cairngorm.samples.popup.model
{
	import mx.collections.IList;
	
	public class Employees
	{
		[Bindable]
		public var employees : IList;
		[Bindable]
		public var hasEmployees : Boolean;		
	}
}

Note that we modify the hasEmployees property of the Empoyees model object.

Let the view react react for you!

And here comes the crux: You can use the mx:Binding tag or the Observe/ObserveValue tag to invoke a view method, once the hasEmployees Boolean value changes.

I’d recommend using the ObserveValue tag to listen to a specific value of a state change of your model. More precisely, you can bind the source property of it to the bindable hasEmployees property of your model object Employees.

<ac:ObserveValue 
	source="{ model.employees.hasEmployees }" 
	handler="{ createEmployeeList }" 
	value="{ true }"/>

You could have also used the Observe tag to listen all updates of the hasEmployees property. For more information, check this out.
The ObserveValue tag above will invoke the createEmployeeList method defined in a Script block of the same MXML file. This method will invoke the popup.

private function createEmployeeList() : void
{
	var application : DisplayObject = DisplayObject( Application.application );
	var popup : IFlexDisplayObject = PopUpManager.createPopUp( application, EmployeeList, true );			
	PopUpManager.centerPopUp( popup );
	var concretePopup : EmployeeList = EmployeeList( popup );
	concretePopup.employees = model.employees;
}

That's it! Through a state change in your model, you view has reacted. Furthermore, you can now let many other objects observing this particular state of your model and they can all act independently.

Cheers!

Posted by auhlmann at 02:02 PM | Comments (19)

September 28, 2006

Cairngorm Sample – How Business Logic Can Manage Views Part IV

In the last post about this topic, I’ve added some functionality and another use case to our stock quote application and showcased how a Cairngorm application could scale with focusing on creating model objects. In this post I’ll perform some further refactorings to our model objects.

Furthermore, our customer wants us to show a pop-up as soon as a successful stock purchase is being made. I’ll cover how a state change in your model can transparently cause view related code (like creating a pop-up) to be executed.

Iteration 6 - Refactorings

In iteration 5 we had a statusMessage field in both model objects, StockQuote and StockPurchase. Two separate text input fields have listened to these properties. Now, let’s only have one status message for both model objects. This i.e. could bring us to the idea of creating another object that encapsulates StockQuote and StockPurchase. Let’s call it StockTransaction:

package com.adobe.cairngorm.samples.dashboard.model
{
	public class StockTransaction
	{
		[Bindable]
		public var stockQuote : StockQuote;		
		[Bindable]
		public var stockPurchase : StockPurchase;		
		[Bindable]
		public var status : int;
		
		public function StockTransaction()
		{
			stockQuote = new StockQuote();
			stockPurchase = new StockPurchase( stockQuote );
		}
	}
}

Note that in the above, I’ve changed the previous statusMessage property; which was typed as a String, to a status property; which is typed as an int. I’ve done that so our model objects can only be concerned with a status of our use case and not with the presentation of this status. I think the presentation of the status is something that our views should be responsible for.

Our model objects can contain the different status options they can be in. i.e. our new StockQuote object can have an ERROR and a SUCCESS status, showing that the StockQuote retrieval was either successful or not. So, we can define the states with two values.

public static const ERROR : int = 1;
public static const SUCCESS : int = 0;

In our new GetStockQuoteCommand we can decide in what state the StockQuote object is currently in. Here’s a snippet of it:

public function onResult( event : * = null ) : void
{
	//for demo purpose: event would normally be an event object of remote service result.
	stockTransaction.stockQuote.lastStockQuote = event as Number;			
	stockTransaction.stockQuote.isPending = false;
	stockTransaction.status = StockQuote.SUCCESS;
}
		
public function onFault( event : * = null ) : void
{
	stockTransaction.stockQuote.lastStockQuote = NaN;
	stockTransaction.status = StockQuote.ERROR;
	stockTransaction.stockQuote.isPending = false;	   	
}

There are various ways to achieve a similar separation of concerns. In this application however, I'll let the StockMarketPod.mxml view listen to the value of status and depending on the status value, StockMarketPod will display a String, which is defined on this view. StockMarketPod uses a helper class called StatusMessageFormatter that translates the status property into the correct String needed by the view. This helper class is initialized like this:

<view:StatusMessageFormatter 
	id="statusFormatter" 
	statusMessages="{ [ '', 'Quote retrieval error.', 'Purchase error.', 'Purchase succesfull.' ] }"/>	

I listen to the status via a function binding on the status property. For more information on this you can take a look into the full source code for this application at the end of this post.

Flex 2 make it easy to perform refactorings like this. This is very important since refactorings of your model objects can occur all the time and most often you rarely get it right the first time around. ;)

Iteration 6 - Invoking View Related Methods

As said in the beginning of this post, our customer wants us to alert the user via popup when a stock purchase request has successfully been made. Let’s think of creating a simple Alert window (mx.controls.Alert). When the onResult handler of the PurchaseStockCommand is invoked we know a stock purchase has been completed.

I think of creating an Alert control as something only views should be concerned about. It’s like creating other view components, managing states of view components or effects. I think only views should be responsible. Usually we bind UI controls to properties of our model objects. But for creating an Alert control, there’s no UI component where we can bind to. How do we best let a view invoke a view related method after a certain state change in our model occurred?

I basically see two ways to do this from a model. We could manipulate a model object so it dispatches an EventDispatcher event that the view can listen to. We could also just mark properties of our model as Bindable and let our views subscribe to either a custom event name that you have defined in our Bindable metadata (with [Bindable(event="customEventName")]), or to the default "propertyChange" event. Using the propertyChange event your view's event handler method would have to differentiate what property changed. You can do that with the passed in mx.events.PropertyChangeEvent object. Listening to a custom event name defined in the Bindable metadata, we have to dispatch the event manually using dispatchEvent.

I see two drawbacks using this approach. First of all, it’s more code to write and most importantly, to read. This is because your views need to subscribe manually to events and define event handlers. It might also affect your models since some of them might need extra EventDispatcher functionality. Secondly, we need to make sure that our model objects exist when the view is subscribing to the event.

The latter is something binding gives us for free. We can bind to a state change of a model even if the model isn’t initialized at the time of subscription. Furthermore, using the binding approach is just quicker to do in many cases.

Following the binding approach, we can bind to a property of our model objects and invoke a method in our views with using the mx:Binding tag.

This could look like the below:

private function set createChargedMessage( value : Boolean ) : void
{
	if( value ) Alert.show( "Thanks! Your bank account has been charged" );
}
<mx:Binding 
	source="stockTransaction.stockPurchase.isComplete" 
	destination="createChargedMessage"/>	

Now, instead of using mx:Binding I recommend that you use an extended version of the Observe tag that Paul Williams originally blogged.

This can make your code more secure, and furthermore it does also give you a slightly easier API to solve the problem here. We only need to listen to the "true" value of the isComplete Boolean. Therefore, we tell our new utility exactly this.

<ac:ObserveValue 
	id="observe" 
	source="{ stockTransaction.stockPurchase.isComplete }" 
	handler="{ createChargedMessage }" 
	value="{ true }"/>

The createChargedMessage will be reduced to this:

private function createChargedMessage() : void
{
	Alert.show( "Thanks! Your bank account has been charged" );
}

For more information on why I’ve introduced this utility class, please read the follow up post on this topic, "Using Binding Securely".

Summary

What have we done here?

We needed our view to invoke a view related method based on some state change in our model. First, we’ve changed the state in the model with invoking a Command. The Command located our model object via ModelLocator and changed the state of this model object via its API. Because our view subscribed to this specific state change (via binding in our case), it could react to it with invoking a method. Since it was using mx:Binding, we didn’t need any extra ActionScript to setup the binding, neither did we need a MXML component that would accept a return value (which we didn’t have since we were calling a popup).

As I’ve noted above, there’s also one little drawback on how we use the binding approach here.

For more information on the drawback and a solution to it with an additional feature please read the next post, "Using Binding Securely"

As after every post in this series, you can view the application and download the source code of it via right mouse click > View Source.

Posted by auhlmann at 11:15 AM | Comments (8)

July 20, 2006

Cairngorm Sample – How Business Logic Can Manage Views Part III

The goal of this series is to demonstrate how you can manage views with investing in your client side business logic.

The previous posts (part 1, part 2) were primarily about the infrastructure of a very simple single and multiple view dashboard application. This and the following posts will add further functionality to the application with the indent to demonstrate how your model objects become a focus point of your application.

In particular, we'll add validation logic, formatting logic and an additional use case.

Iteration 4 – Adding Functionality

Let's go back to our single view dashboard and let's first make this application a bit more realistic with simulating a delay for the dummy remote service that is invoked after the user presses the "Get Quote" button. I'll just add a flash.utils.setTimeout method in our iteration 2 delegate (StockMarketDelegate.as) to simulate a delay.

Usually, while a remote service is being processed, the UI is supposed to react to it with i.e. displaying a progress or disabling certain UI components to let the user know that further requests cannot be dispatched. In our use case we'd like to disable the "Get Quote" button while a remote service call is being processed. We can achieve this easily with adding a property to our representing model object from iteration 2 StockQuote. Let's call it isPending and make it a Boolean where our view can bind to.

[Bindable]
public var isPending : Boolean;

Adding Validation Logic

Let's add a validation of our stock quote with inspecting the actual stock quote value. We can easily use the mx.validators.StringValidator of the Flex Framework for this type of work.

In our application we need both parameters of validations (isPending and the result of StringValidator) to pass in order to let the UI allow a remote service request. Our model object could have an isValid property, which combines these two parameters of validation. I compute isValid with the following method.

private function validate() : void
{
	isValid = ( isSymbolValid && !isPending );
}

isSymbolValid is a property, which will be the result of our StringValidator.

We could add the mx:StringValidator tag into the StockMarketPod.mxml and have it calling the StockQuote model object on its "valid" and "invalid" events.

<mx:StringValidator 
	minLength="2" triggerEvent="change" 
	source="{ symbolTextInput }" property="text"
	valid="stockQuote.validateSymbol( true );" 
	invalid="stockQuote.validateSymbol( false );"/>

To call a model object directly from the view isn't the clearest form of MVC since views should only dispatch events, which are handled by your controller. But for this example I find it good enough to have a method be called directly from the view. In my humble opinion this use case doesn't justify a "round trip" to the model object just yet. Think: "The simplest thing that could possibly work".

Adding Formatting Logic

This is easy. We want to have our stock quote result being formatted with a currency formatter. You could define the formatter in StockMarketPod.mxml with

<mx:CurrencyFormatter 
	id="standardEuroFormatter"  
	currencySymbol="€" precision="2"/>

and have it formatting with a method binding

<mx:Label text="{ standardEuroFormatter.format( stockQuote.lastStockQuote ) }"/>

Now, let's have a complete look into our StockQuote model object.

package com.adobe.cairngorm.samples.dashboard.model
{
	public class StockQuote
	{
		[Bindable]
		public var lastStockQuote : Number;
		[Bindable]
		public var isValid : Boolean;
		[Bindable]
		public var statusMessage : String;
				
		private var _isPending : Boolean;
		private var isSymbolValid : Boolean;
                
		[Bindable]
		public function get isPending() : Boolean
		{
			return _isPending;
		}
		
		public function set isPending( value : Boolean ) : void
		{
			_isPending = value;
			validate();
		}
			
		public function validateSymbol( isValid : Boolean ) : void
		{
			isSymbolValid = isValid;
			validate();
		}
		
		private function validate() : void
		{
			isValid = ( isSymbolValid && !isPending );
		}		
	}
}

The new isPending state is being manipulated by the GetStockQuoteCommand.

private var model : ModelLocator = ModelLocator.getInstance();
private var stockQuote : StockQuote = model.stockQuote;
	 	
public function execute( event : CairngormEvent ) : void
{
	stockQuote.isPending = true;
	     	
	var stockQuoteEvent : GetStockQuoteEvent = GetStockQuoteEvent( event );	      
	var symbol : String = stockQuoteEvent.symbol;
	var delegate : StockMarketDelegate = new StockMarketDelegate( this );
	delegate.getQuoteForSymbol( symbol );    
}
		
public function onResult( event : * = null ) : void
{
	//for demo purpose: event would normally be an event object of remote service result.			
	stockQuote.lastStockQuote = event as Number;			
	stockQuote.isPending = false;
	stockQuote.statusMessage = "";
}
		
public function onFault( event : * = null ) : void
{
	stockQuote.lastStockQuote = NaN;
	stockQuote.statusMessage = "Quote retrieval error.";
	stockQuote.isPending = false;
}

And let's finally look into our view StockMarketPod.mxml

<?xml version="1.0" encoding="utf-8"?>
<mx:Panel 
	xmlns:mx="http://www.adobe.com/2006/mxml"
	xmlns:util="com.adobe.cairngorm.samples.dashboard.util.*">
        
	<mx:Script>
		<![CDATA[
			import com.adobe.cairngorm.control.CairngormEventDispatcher;
			import com.adobe.cairngorm.samples.dashboard.model.StockQuote;
			import com.adobe.cairngorm.samples.dashboard.events.GetStockQuoteEvent;	
			
			[Bindable]
			public var stockQuote : StockQuote;
			
			private function getQuoteForSymbol() : void
			{
				var event : GetStockQuoteEvent = new GetStockQuoteEvent( symbolTextInput.text );
				CairngormEventDispatcher.getInstance().dispatchEvent( event );
			}
		]]>
	</mx:Script>
	
	<mx:CurrencyFormatter 
                id="standardEuroFormatter"  
		currencySymbol="€" precision="2"/>
		
	<mx:StringValidator 
		minLength="2" triggerEvent="change" 
		source="{ symbolTextInput }" property="text"
		valid="stockQuote.validateSymbol( true );" 
		invalid="stockQuote.validateSymbol( false );"/>
	
	<mx:Form>
	
		<mx:FormItem label="Symbol">
			<mx:TextInput 
				id="symbolTextInput"/>
			<mx:Button 
				label="Get Quote" 
				enabled="{ stockQuote.isValid }" 
				click="getQuoteForSymbol();"/>
		</mx:FormItem>
				
		<mx:FormItem label="Price Quote">
			<mx:Label text="{ standardEuroFormatter.format( stockQuote.lastStockQuote ) }"/>
			<mx:Label text="{ stockQuote.statusMessage }"/>
		</mx:FormItem>
		
	</mx:Form>	
</mx:Panel>


Note, that this version of StockMarketPod.mxml doesn't have a reference to ModelLocator anymore. Instead, the model objects, which are needed, are passed into StockMarketPod.mxml.

<mx:Script>
	<![CDATA[
		import com.adobe.cairngorm.samples.dashboard.model.ModelLocator;
		import com.adobe.cairngorm.samples.dashboard.model.StockQuote;
									
		[Bindable]
		private var model : ModelLocator = ModelLocator.getInstance();
		[Bindable]
		private var stockQuote : StockQuote = model.stockQuote;	
	]]>
</mx:Script>
 
<view:StockMarketPod 
	stockQuote="{ stockQuote }" 
	title="Stockmarket Pod"/>

I find it beneficial to give view components only the information they need instead of global access to all your business logic through a reference of your ModelLocator. Furthermore, this makes view components more reusable across projects.

Optionally; to further increase view reusability, we could also replace CairngormEventDispatcher calls inside StockMarketPod.mxml with view events dispatched via EventDispatcher. We would handle these events outside the view component that we want to make reusable across applications and dispatch the Cairngorm event using CairngormEventDispatcher in order to reach application specific Cairngorm commands.

Download and "view source" the complete iteration 4 example.

Iteration 5 – Adding a Use Case

Let's pretend our customer now wants us to also purchase stocks after a stock quote has been retrieved. We are supposed to add another text input field with a quantity field that defines how many stocks the user would want to buy. Furthermore, we would need a purchase button. The purchase button must only be enabled when the user is allowed purchase a stock quote. This is the case when:

  • the quantity field contains a valid value.
  • another stock purchase request is not pending.
  • a stock quote has been successfully retrieved.

We could use another model object responsible for managing all this. Let's call it StockPurchase. StockPurchase can have a very similar structure than StockQuote. But the third bullet point requires StockPurchase to know about StockQuote's state. Only if StockQuote has been completed, StockPurchase's validation can pass completely. There are various ways to let StockPurchase know about StockQuote's state. For this example I just pass a reference of StockQuote to the constructor of StockPurchase. I'll do that inside an initialize method (here in ModelLocator). This is being called at start up.

private function initialize() : void
{
	stockQuote = new StockQuote();
	stockPurchase = new StockPurchase( stockQuote );   		
}

Here is the full source code of StockPurchase.

And here are the additions to StockMarketPod.mxml including the additional validator and formatter.

A new PurchaseStockCommand finally sends the remote purchase request, handles the response and manipulates the client side model objects accordingly. Our views listen to model object events through bindings and translate the model data according to view related rules. This translation process could be done using view helpers like the mx:CurrencyFormatter used in this sample.

Download and "view source" the complete iteration 5 example.

I hope you've enjoyed my dashboard iterations so far. The most important point I wanted to bring across is how valuable I think it is to think about our model objects and try to extract much functionality out of our views. Going forward this way, I find it's much easier to build unit-testable, flexible and scalable Rich Internet Applications.

In the next iteration I'll cover some additional refactorings on the view and model objects and how state changes in the model can cause view related methods to be executed.

Posted by auhlmann at 11:47 AM | Comments (6)

July 05, 2006

Cairngorm 2 (for Flex 2) – Simple Sample Applications

UPDATE: The Login example has been updated by Neil Webb to Cairngorm 2.2. Check out his series of articles explaining Cairngorm using the Login example.

I'd like to give you early access to a new version of the CairngormLogin sample, which we'll ultimately make available on Adobe Labs. We’ve been releasing this very simple example application in earlier versions of Cairngorm alongside the framework code. Intention is to showcase how to get a simple Cairngorm application running. Furthermore, I’ve also upgraded my dashboard samples, which I’ve shown in previous posts.

I’ve done some little additions to the CairngormLogin sample that hopefully show a little more closeness to a real world Cairngorm application. There’s now a model object Login, which encapsulates login related states of the application. This object also contains a pending state, which a LoginPanel view listens to in order to prevent multiple login requests being fired before a running request has been returned. Also, there’s a delay in the delegate to simulate a remote service response. The code needed to call a remote service via RemoteObject is commented.

I’ve also updated the Dashboard examples to work with the latest Cairngorm release. Expect some further updates that actually add some real functionality to this in future.

see and download (right click and view source) the samples here:

As Steven Webster mentioned previously, expect more examples from us coming up soon and let us know when you’ve examples to show. I think the more the better!

Posted by auhlmann at 04:39 PM | Comments (29)

July 02, 2006

Cairngorm 2 (for Flex 2) – Overview and Migration Path

Earlier this week Cairngorm for Flex 2 has been released. There have been a number of changes compared to Cairngorm for Flex Beta 3. This post intents to explain some of what’s changed, why it changed and how you can handle the changes for migrating your Cairngorm applications to the newest version. Sorry for the delay of this post, but as you might know, there’s a fantastic soccer World Cup in Germany going on these days. ;)

1. Responder interface now optional and typeless.

The signature of Responder has changed to an optional typeless (wildcard type '*') from the mx.rpc.events.ResultEvent and mx.rpc.events.FaultEvent signatures. Here are three reasons for this:

  • You can now handle RemoteService, WebService, RemoteObject and non Flex framework remoting responses.
  • You can now more easily perform spikes (prototypes) to simulate a server side response. Just place stub data into the delegate and return that to the onResult handler of your Command. Since it’s optional, you don’t have to return any data to the Command.
  • If you unit test your Commands, this signature might also be handy.

2. Minor improvements

There have been a number of minor improvements i.e. Cairngorm now prevents to add an already registered Command, the ViewHelper adds and removes itself from the ViewLocator once added or removed from the display list and other minor code refactorings have been performed as well.

3. New Cairngorm Event Dispatcher

This is the most significant change. For Cairngorm Flex 1 users this is familiar as it basically is EventBroadcaster with a more “Flex 2 EventDispatcher-like” syntax.

Here are the corresponding release notes:

In the beta releases of Cairngorm for Flex 2, we departed from the Cairngorm 0.99 approach of dispatching events, and used the new built-in dispatchEvent method with bubbling available on Flex components. However, since bubbling is only available on the display list we had to listen to a top level child on the display list for all events, which we decided to be mx.core.Application.application. In cases where a view reference on the display list below mx.core.Application.application is not available, users had to obtain mx.core.Application.application directly in order to dispatch a Cairngorm event. This was often the case in for example pop-ups and other ActionScript classes without the appropiate view reference. Our experience within Adobe Consulting in delivering Flex 2 projects, coupled with community feedback, has encouraged us to revert to an architecture more like the original Cairngorm 0.99 architecture using EventBroadcaster.

This is indented to encourage developers to have a clear separation of Cairngorm events and application events. The new CairngormEventDispatcher offers exactly this, since only event objects of type CairngormEvent can be dispatched and handled. Furthermore, CairngormEvent has the typeless data property for lazy people. ;) For more information about CairngormEventDispatcher, check out the ASDocs, temporarily available through Alistair McLeod’s blog.

Migrating your Cairngorm Application.

The Flex 2 compiler helps you most with migrating your application. For example it will tell you that the new Responder interface might not be compatible to your pre Cairngorm for Flex 2 application.

However, for larger applications a migration of the event dispatching system from legacy Cairngorm Flex beta Application.application event dispatching to the new event dispatching system using CairngormEventDispatcher could be a bit trickier to accomplish.

The reason is that there could be Cairngorm events that trigger and modify various things while they bubble up the display list till they arrive to the root of the display list and the FrontController. If you would just replace the dispatchEvent calls from the view with CairngormEventDispatcher calls, code that relies on bubbling of events will break. Therefore; in larger applications, this migration should probably not be done in one step.

I've created a little migration Front Controller, which can use both techniques simultaneously, so developers can replace this event dispatching code carefully step by step. Here is a potential migration path that you could perform:

  • Delete all legacy Cairngorm framework code with the updated framework. Since it’s a package change, Flex Builder will make it easy for you to spot the places in the application that have to be changed due to Cairngorm 2.
  • Now, here’s the ugly part: In order to make this migration work, you’ve got to temporarily change a Cairngorm source file. Open com.adobe.cairngorm.control.CairngormEvent and set the default value of the bubbles property to true. Make sure you reverse this change after your migration. Or better: Use the Cairngorm framework ActionScript source files while you are in a migration period and replace all Cairngorm framework source files with the Cairngorm SWC file that accompanies the latest release of Cairngorm.
  • Let your application FrontController extend com.adobe.cairngorm.control.MigrationFrontController instead of com.adobe.cairngorm.control.FrontController.

When you now run the application, you’ll receive logging statements if the deprecated cairngorm event dispatching call is invoked. This logging statement can help you to locate the event and help you to make a decision if it can be replaced without side effects.

using trace

---------------------------------------------------------------------------
 MigrationFrontController.handleDeprecated: Deprecated event dispatching used with:
			event object: com.adobe.cairngorm.samples.dashboard.events::GetStockQuoteEvent
			event.type: EVENT_GET_STOCK_QUOTE
			event.target: Dashboard0.StockMarketDashboard5._StockMarketPod1
			event source: com.adobe.cairngorm.samples.dashboard.view::StockMarketPod
			event.currentTarget: Dashboard0

or using mx.logging:

10:27:59.760 [INFO] com.adobe.cairngorm.samples.dashboard.control.DashboardController 
---------------------------------------------------------------------------
			MigrationFrontController.handleDeprecated: Deprecated event dispatching used with:
			event object: com.adobe.cairngorm.samples.dashboard.events::GetStockQuoteEvent
			event.type: EVENT_GET_STOCK_QUOTE
			event.target: Dashboard0.StockMarketDashboard5._StockMarketPod1
			event source: com.adobe.cairngorm.samples.dashboard.view::StockMarketPod
			event.currentTarget: Dashboard0


Download MigrationFrontController here.

Posted by auhlmann at 03:47 AM | Comments (3)

June 18, 2006

Cairngorm Sample – How Business Logic Can Manage Views Part II

In the last post I’ve introduced you to the stock market dashboard sample application and how you can create business logic to manage your view. In this post and example, I’ll apply another use case to the sample application. With adding multiple pods to the stock market dashboard, the user can request price quotes independently. Each pod has its own stock market data. I’ll cover how your business logic can be adjusted to manage each view. I’ll also touch on how stateless Commands can help making this application easier.

Iteration 3 – Creating business logic that manages multiple views

Often, I find use cases where many views of a same type share common functionality. Each view can be represented by a model object. The view can request the appropriate model object from a manager object at start up. For example the StockMarketDashboard.mxml would look like this with additional pods:

<view:StockMarketPod 
	quoteId="quote1" 
	title="Stockmarket first pod"/>
<view:StockMarketPod 
	quoteId="quote2" 
	title="Stockmarket second pod"/>
<view:StockMarketPod 
	quoteId="quote3" 
	title="Stockmarket third pod"/>
<view:StockMarketPod 
	quoteId="quote4" 
	title="Stockmarket fourth pod"/>

Notice that the view needs to send some form of unique identifier to the manager. We add this unique identifier to our StockQuote class.

package org.nevis.cairngorm.samples.dashboard.model
{
	public class StockQuote
	{
		[Bindable]
		public var lastStockQuote : Number;
		[Bindable]
		public var stockQuoteError : String;		
	}
}

The manager object StockQuoteManager, is declared at initialization on the ModelLocator instance. But how does this manager object look like?

Usually, I implement it with a hash map that is keyed by the view’s unique identifier. In the constructor we can initialize the hash map. In Flex 1.x we would have used a generic Object to create a hash map. In Flex 2, I like to use flash.utils.Dictionary, which more clearly shows the indent of the code.

public function StockQuoteManager() 
{
	stockQuotes = new Dictionary();
}

The getStockQuote method either creates or returns a specific StockQuote object based on the view’s unique identifier quoteId.

public function getStockQuote( quoteId : String ) : StockQuote
{
	var key : String = quoteId;
	if( stockQuotes[ key ] == null )
	{
		var stockQuote : StockQuote = new StockQuote();
		stockQuotes[ key ] = stockQuote;
	}
	return StockQuote( stockQuotes[ key ] );
}

The StockMarketPod.mxml almost looks identical to the old version of iteration 2, which refactored ModelLocator properties to the StockQuote model object. We just need to send the unique identifier to the Command, since the Command itself needs to retrieve a StockQuote object from the manager object. So the GetStockQuoteEvent class gets another quoteId property.

In GetStockQuoteCommand we need to store the unique identifier as an instance property since the command needs to retrieve and update the correct StockQuote object in an onResult or onFault handler.

private var model : ModelLocator = ModelLocator.getInstance();
private var quoteId : String;
	 	
public function execute( event : CairngormEvent ) : void
{
	var stockQuoteEvent : GetStockQuoteEvent = GetStockQuoteEvent( event );	     	
	var symbol : String = stockQuoteEvent.symbol;
	quoteId = stockQuoteEvent.quoteId;
	var delegate : StockMarketDelegate = new StockMarketDelegate( this );
	delegate.getQuoteForSymbol( symbol );
}
		
public function onResult( event : ResultEvent = null ) : void
{
	//simulate a result from service
	var lastStockQuote : Number = Math.random() * 50 + 5;
	var stockQuote : StockQuote = model.stockQuoteManager.getStockQuote( quoteId );
	stockQuote.lastStockQuote = lastStockQuote;
	stockQuote.stockQuoteError = "";
}
		
public function onFault( event : FaultEvent = null ) : void
{
	var stockQuote : StockQuote = model.stockQuoteManager.getStockQuote( quoteId );
	stockQuote.lastStockQuote = NaN;
	stockQuote.stockQuoteError = "An error occured.";
}

Also, notice that in the example above we make use of stateless commands. Since the introduction of stateless commands in Cairngorm, new command instances are being created each time a Cairngorm event is dispatched (see Alistair McLeod’s blog entry). You can now securely save instance properties in your commands without fearing that the instance property will be modified by another Cairngorm event. Would your command not be stateless, our quoteId could be overwritten by a quoteId from another stock pod. When the command's onResult is triggered by the incoming response from the first remote service request, the response would map to a wrong StockQuote object. But thankfully, we don't need to think about that anymore. ;)

Spinning it a wee bit further.

Notice, that your manager object could contain other functionality that applies to all pods. Think about a domain model that suits your use case best.

Furthermore, in a slightly more elaborate use case it might be significant that the onResult handler in GetStockQuoteCommand requests the currently up-to-date StockQuote object from the manager object as it does in the example shown. While the command is waiting for a response for a request to a remote service, the state of the RIA could change. When the response comes in, the manager object might want to return a different StockQuote object or not a StockQuote object at all.

For example, the end user might be able to close certain stock quote pods. In this case, its representing StockQuote model object may also have been deleted. On subsequent requests to the StockQuote object, the manager object might want to throw an exception. This exception might be handled differently by the command.

You might want to check out the complete source (right click on the sample app and choose “View Source” or download it as a ZIP file).

Posted by auhlmann at 04:21 PM | Comments (12)

June 01, 2006

Cairngorm Sample – How Business Logic Can Manage Views

There are many ways to update your views when your server- or client side business logic changes. The strategy you find me usually recommending is the ModelLocator strategy, which leverages the Flex binding feature. See Steven Webster's article for more information.

Basically, your views bind to properties that are retrieved from your ModelLocator. These properties can be changed from your Commands, other business logic or other views and once changed; all listening views are updated seamlessly.

Since many of the Cairngorm examples out there are meant to be easy to understand, they often just show these properties exposed as single properties on the ModelLocator. Once your RIA grows in size, this indeed can be quite limiting. I’ll showcase another Cairngorm sample application that focuses on how you can improve your architecture with investing in slightly more advanced business logic. The sample application is a stock market dashboard that allows users to retrieve price quotes on a company stock. From time to time I’ll add features to this application, to showcase some possible ways to architect a Cairngorm application. This version of it will use Cairngorm for Flex 2 Beta 3.

Iteration 1 - The simplest thing that could possibly work

But let’s first look at the very simplest possible solution with still leveraging MVC principles advocated by Cairngorm. The stock market dashboard in its first iteration just contains a single pod with minimal UI controls. See example.

With pressing the “Get Quote” button the user dispatches a Cairngorm event that is handled by a Command instance to request a new quote.

StockMarketPod.mxml dispatches the Cairngorm event on behalf of a Button’s click event:

<mx:Button label="Get Quote" click="getQuoteForSymbol();"/>

And the corresponding mx:Script tag:

import org.nevis.cairngorm.samples.dashboard.events.GetStockQuoteEvent;									
private function getQuoteForSymbol() : void
{
	var event : GetStockQuoteEvent = new GetStockQuoteEvent( symbolTextInput.text );
	dispatchEvent( event );
}

GetStockQuoteCommand handles the Cairngorm event and asks a business delegate class (StockMarketDelegate) for a quote.

public function execute( event : CairngormEvent ) : void
{
	var symbol : String = GetStockQuoteEvent( event ).symbol;
	var delegate : StockMarketDelegate = new StockMarketDelegate( this );
	delegate.getQuoteForSymbol( symbol );
}

In a real world scenario this would be most likely a server side call, but for the sake of simplicity in this demo, I’ve commented the code that would be needed to call a remote service in the business delegate.

StockMarketDelegate.as just calls back to the Command immediately. Notice that if the user enters a symbol “fail”, the Command’s onFault handler will be invoked.

public function StockMarketDelegate( responder : Responder )
{
	//disabled for demo
	//this.service = ServiceLocator.getInstance().getService( "stockMarketDelegate" );
	this.responder = responder;
}
		
public function getQuoteForSymbol( symbol : String ) : void
{
	//disabled for demo
	//var call : AsyncToken = service.getQuoteForSymbol( symbol );
	//call.resultHandler = responder.onResult;
	//call.faultHandler = responder.onFault;
	if( symbol == "fail" )
	{
		responder.onFault();
	}
	else
	{
		responder.onResult();
	}
}

Our StockMarketPod view just needs two information from the application’s business logic.

  • The answer to the quote (the stock price)
  • An error message, that communicates to the user if a request fails.

Because this is so simple, I have just created two properties directly on the ModelLocator.

public var lastStockQuote : Number;
public var stockQuoteError : String;

GetStockQuoteCommand can then just change these values in the onResult or onFault handler. For this demo purpose, it performs some dummy calculations to simulate the remote service result.

public function onResult( event : ResultEvent = null ) : void
{
	//simulate a result from service
	var stockQuote : Number = Math.random() * 50 + 5;
	model.lastStockQuote = stockQuote;
	model.stockQuoteError = "";
 }
		
public function onFault( event : FaultEvent = null ) : void
{
	model.lastStockQuote = NaN;
	model.stockQuoteError = "An error occured.";
}

The StockMarketPod view can bind to those properties and further format the result based on its presentation needs:

<mx:FormItem label="Symbol">
	<mx:Label text="{ formatQuote( model.lastStockQuote ) }"/>
</mx:FormItem>
<mx:FormItem>
	<mx:Label text="{ model.stockQuoteError }"/>
</mx:FormItem>

And the formatting logic of a mx:Script block.

private function formatQuote( quote : Number ) : String
{
	return ( isNaN( quote ) ) ? "" : String( quote );
}

Note that this type of code could be refactored and extracted out from the view into a unit-testable utility class.

Iteration 2 - Creating business logic that fit your needs

Now, the above example is the simplest thing that could possibly work. As your RIA grows in size this can be limiting. You may find that your ModelLocator instance is “overcrowded” with properties that you might even forget what they are needed for. You might also run into naming conflicts with other properties designed for different use cases.

A common refactoring is to create business objects that encapsulate the properties of your use case. These business objects can represent the information that your views need in a business context. You can design them at a granularity that fits your use case best. For example in a larger RIA you might want to think about a number of classes to represent your use case. Your views may then bind to these business objects or to properties of it. Your views might bind to other application objects that translate your business objects to a form that’s more useful to your views.

This way, your ModelLocator might better communicate its intent as it’s much easier for developers to grasp what business logic your application contains.

In our stock market dashboard, we could e.g. encapsulate the lastStockQuote and stockQuoteError properties in a simple business object:

package org.nevis.cairngorm.samples.dashboard.model
{
	public class StockQuote
	{
		[Bindable]
		public var lastStockQuote : Number;
		[Bindable]
		public var stockQuoteError : String;
	}
}

Note, that in a true business object you would probably not even have a property that directly holds a String with an error message. Instead, the business object would just hold an error type and other application objects would translate that error type to e.g. a String representation, which could then be used by views.

Our ModelLocator would just define a single property representing a stock market pod view in a business context. E.g. with

public var stockQuote : StockQuote = new StockQuote ();

And our view would change to this:

<mx:FormItem label="Symbol">
	<mx:Label text="{ formatQuote( model.stockQuote.lastStockQuote ) }"/>
</mx:FormItem>
<mx:FormItem>
	<mx:Label text="{ model.stockQuote.stockQuoteError }"/>
</mx:FormItem>

You might want to check out the complete source (right click on the sample app and choose “View Source” or download it as a ZIP file).

Posted by auhlmann at 12:20 PM | Comments (9)