« June 2006 | Main | September 2006 »
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:
- CairngormLogin sample
- Dashboard sample iteration 1
- Dashboard sample iteration 2
- Dashboard sample iteration 3
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)
